Compare commits

..
12 Commits
Author SHA1 Message Date
Jast 4a65e23b9b Merge branch 'master' into new-wall-2 2025-01-26 13:09:06 +08:00
tomsun28 49da06219f [doc] update 2025-01-25 00:17:54 +08:00
tomsun28 8e426f391d [doc] update wall
Signed-off-by: tomsun28 <tomsun28@outlook.com>
2025-01-25 00:09:35 +08:00
tomsun28 e8bec5d890 Add @myangle1120 as a contributor 2025-01-25 00:08:15 +08:00
tomsun28 045f2e165a Add @NikhilMurugesan as a contributor 2025-01-25 00:08:05 +08:00
tomsun28 d3bfd65686 Add @jonasHanhan as a contributor 2025-01-25 00:07:55 +08:00
tomsun28 8b82986064 Update @wanhao23 as a contributor 2025-01-25 00:07:44 +08:00
tomsun28 7b129d5ead Add @wanhao23 as a contributor 2025-01-25 00:07:34 +08:00
tomsun28 fb07107171 Add @MasamiYui as a contributor 2025-01-25 00:07:23 +08:00
tomsun28 0dc79bc6c7 Add @MonsterChenzhuo as a contributor 2025-01-25 00:07:13 +08:00
tomsun28 19c0b78e2a Add @pjfanning as a contributor 2025-01-25 00:07:01 +08:00
tomsun28 7528db346d Add @helei1030 as a contributor 2025-01-25 00:06:52 +08:00
106 changed files with 642 additions and 3913 deletions
+1 -1
View File
@@ -73,7 +73,7 @@ jobs:
# upload application logs
- name: Upload logs & API test reports
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
if: always()
with:
name: hz-logs-${{ github.run_id }}
+2 -2
View File
@@ -39,7 +39,7 @@ jobs:
run: npx docusaurus-prince-pdf -u https://hertzbeat.apache.org/docs --output docs-en.pdf
- name: Upload results
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with:
name: docs-cn-pdf
path: docs-cn.pdf
@@ -47,7 +47,7 @@ jobs:
retention-days: 1
- name: Upload results
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with:
name: docs-en-pdf
path: docs-en.pdf
-5
View File
@@ -29,11 +29,6 @@
"type": 0,
"paramValue": 1000
},
{
"field": "ssl",
"type": 1,
"paramValue": false
},
{
"field": "username",
"type": 1
-5
View File
@@ -40,11 +40,6 @@
<groupId>org.apache.hertzbeat</groupId>
<artifactId>hertzbeat-plugin</artifactId>
</dependency>
<!-- warehouse -->
<dependency>
<groupId>org.apache.hertzbeat</groupId>
<artifactId>hertzbeat-warehouse</artifactId>
</dependency>
<!-- spring -->
<dependency>
<groupId>org.springframework.boot</groupId>
@@ -17,172 +17,90 @@
package org.apache.hertzbeat.alert.calculate;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Objects;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.alert.reduce.AlarmCommonReduce;
import org.apache.hertzbeat.alert.service.DataSourceService;
import org.apache.hertzbeat.alert.util.AlertTemplateUtil;
import org.apache.hertzbeat.common.constants.CommonConstants;
import org.apache.hertzbeat.common.entity.alerter.AlertDefine;
import org.apache.hertzbeat.common.entity.alerter.SingleAlert;
import org.apache.hertzbeat.common.util.JexlExpressionRunner;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.Collections;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Component;
/**
* Periodic Alert Calculator
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class PeriodicAlertCalculator {
private static final String VALUE = "__value__";
private static final String TIMESTAMP = "__timestamp__";
private final DataSourceService dataSourceService;
private final AlarmCommonReduce alarmCommonReduce;
/**
* The alarm in the process is triggered
* key - labels fingerprint
*/
private final Map<String, SingleAlert> pendingAlertMap;
/**
* The not recover alert
* key - labels fingerprint
*/
private final Map<String, SingleAlert> firingAlertMap;
private final JexlExpressionRunner expressionRunner;
private final Map<String, SingleAlert> notRecoveredAlertMap = new ConcurrentHashMap<>(16);
public PeriodicAlertCalculator(DataSourceService dataSourceService, AlarmCommonReduce alarmCommonReduce) {
this.dataSourceService = dataSourceService;
this.alarmCommonReduce = alarmCommonReduce;
this.pendingAlertMap = new ConcurrentHashMap<>(8);
this.firingAlertMap = new ConcurrentHashMap<>(8);
}
public void calculate(AlertDefine rule) {
public List<SingleAlert> calculate(AlertDefine rule) {
if (!rule.isEnable() || StringUtils.isEmpty(rule.getExpr())) {
log.error("Periodic rule {} is disabled or expression is empty", rule.getName());
return;
return Collections.emptyList();
}
long currentTimeMilli = System.currentTimeMillis();
// todo: implement the following logic
try {
// for prometheus is instant promql query, for db is sql query
// result: [{'value': 100, 'timestamp': 1343554, 'instance': 'node1'},{'value': 200, 'timestamp': 1343555, 'instance': 'node2'}]
// the return result should be matched with threshold
try {
List<Map<String, Object>> results = dataSourceService.calculate(
rule.getDatasource(),
rule.getExpr()
);
// if no match the expr threshold, the results item map {'value': null} should be null and others field keep
// if results has multi list, should trigger multi alert
if (CollectionUtils.isEmpty(results)) {
return;
}
for (Map<String, Object> result : results) {
Map<String, String> fingerPrints = new HashMap<>(8);
// here use the alert name as finger, not care the alert name may be changed
fingerPrints.put(CommonConstants.LABEL_ALERT_NAME, rule.getName());
fingerPrints.putAll(rule.getLabels());
for (Map.Entry<String, Object> entry : result.entrySet()) {
if (entry.getValue() != null && !VALUE.equals(entry.getKey())
&& !TIMESTAMP.equals(entry.getKey())) {
fingerPrints.put(entry.getKey(), entry.getValue().toString());
}
}
if (result.get(VALUE) == null) {
// recovery the alert
handleRecoveredAlert(fingerPrints);
continue;
}
Map<String, Object> fieldValueMap = new HashMap<>(8);
fieldValueMap.putAll(rule.getLabels());
fieldValueMap.put(CommonConstants.LABEL_ALERT_NAME, rule.getName());
for (Map.Entry<String, Object> entry : result.entrySet()) {
if (entry.getValue() != null) {
fieldValueMap.put(entry.getKey(), entry.getValue());
}
}
afterThresholdRuleMatch(currentTimeMilli, fingerPrints, fieldValueMap, rule);
}
} catch (Exception ignored) {
// ignore the query exception eg: no result, timeout, etc
return;
// Execute query
List<Map<String, Object>> queryResults = dataSourceService.query(
rule.getDatasource(),
rule.getExpr()
);
if (CollectionUtils.isEmpty(queryResults)) {
return Collections.emptyList();
}
// Execute expression calculation on query results
List<SingleAlert> newAlerts = queryResults.stream()
.filter(result -> execAlertExpression(result, rule.getExpr()))
.map(result -> buildAlert(rule, result))
.collect(Collectors.toList());
// Handle recovery notification
if (newAlerts.isEmpty()) {
return handleAlertRecover(rule);
}
return newAlerts;
} catch (Exception e) {
log.error("Calculate periodic rule {} failed: {}", rule.getName(), e.getMessage());
return Collections.emptyList();
}
}
private void afterThresholdRuleMatch(long currentTimeMilli, Map<String, String> fingerPrints,
Map<String, Object> fieldValueMap, AlertDefine define) {
String fingerprint = calculateFingerprint(fingerPrints);
SingleAlert existingAlert = pendingAlertMap.get(fingerprint);
Map<String, String> labels = new HashMap<>(8);
fieldValueMap.putAll(define.getLabels());
labels.putAll(fingerPrints);
int requiredTimes = define.getTimes() == null ? 1 : define.getTimes();
if (existingAlert == null) {
// First time triggering alert, create new alert and set to pending status
SingleAlert newAlert = SingleAlert.builder()
.labels(labels)
// todo render var content in annotations
.annotations(define.getAnnotations())
.content(AlertTemplateUtil.render(define.getTemplate(), fieldValueMap))
.status(CommonConstants.ALERT_STATUS_PENDING)
.triggerTimes(1)
.startAt(currentTimeMilli)
.activeAt(currentTimeMilli)
.build();
// If required trigger times is 1, set to firing status directly
if (requiredTimes <= 1) {
newAlert.setStatus(CommonConstants.ALERT_STATUS_FIRING);
firingAlertMap.put(fingerprint, newAlert);
alarmCommonReduce.reduceAndSendAlarm(newAlert.clone());
} else {
// Otherwise put into pending queue first
pendingAlertMap.put(fingerprint, newAlert);
}
} else {
// Update existing alert
existingAlert.setTriggerTimes(existingAlert.getTriggerTimes() + 1);
existingAlert.setActiveAt(currentTimeMilli);
// Check if required trigger times reached
if (existingAlert.getStatus().equals(CommonConstants.ALERT_STATUS_PENDING) && existingAlert.getTriggerTimes() >= requiredTimes) {
// Reached trigger times threshold, change to firing status
pendingAlertMap.remove(fingerprint);
existingAlert.setStatus(CommonConstants.ALERT_STATUS_FIRING);
firingAlertMap.put(fingerprint, existingAlert);
alarmCommonReduce.reduceAndSendAlarm(existingAlert.clone());
}
}
private boolean execAlertExpression(Map<String, Object> result, String expr) {
return false;
}
private void handleRecoveredAlert(Map<String, String> fingerprints) {
String fingerprint = calculateFingerprint(fingerprints);
SingleAlert firingAlert = firingAlertMap.remove(fingerprint);
private SingleAlert buildAlert(AlertDefine rule, Map<String, Object> metrics) {
return SingleAlert.builder()
.labels(rule.getLabels())
.annotations(rule.getAnnotations())
.triggerTimes(1)
.startAt(System.currentTimeMillis())
.activeAt(System.currentTimeMillis())
.build();
}
private List<SingleAlert> handleAlertRecover(AlertDefine rule) {
SingleAlert firingAlert = notRecoveredAlertMap.remove(rule.getId().toString());
if (firingAlert != null) {
// todo consider multi times to tig for resolved alert
firingAlert.setTriggerTimes(1);
firingAlert.setEndAt(System.currentTimeMillis());
firingAlert.setStatus(CommonConstants.ALERT_STATUS_RESOLVED);
alarmCommonReduce.reduceAndSendAlarm(firingAlert.clone());
return Collections.singletonList(buildResolvedAlert(rule, firingAlert));
}
pendingAlertMap.remove(fingerprint);
return Collections.emptyList();
}
private String calculateFingerprint(Map<String, String> fingerPrints) {
List<String> keyList = fingerPrints.keySet().stream().filter(Objects::nonNull).sorted().toList();
List<String> valueList = fingerPrints.values().stream().filter(Objects::nonNull).sorted().toList();
return Arrays.hashCode(keyList.toArray(new String[0])) + "-"
+ Arrays.hashCode(valueList.toArray(new String[0]));
private SingleAlert buildResolvedAlert(AlertDefine rule, SingleAlert firingAlert) {
return null;
}
}
@@ -17,79 +17,13 @@
package org.apache.hertzbeat.alert.calculate;
import static org.apache.hertzbeat.common.constants.CommonConstants.ALERT_THRESHOLD_TYPE_PERIODIC;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ThreadFactory;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.alert.dao.AlertDefineDao;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import org.apache.hertzbeat.common.entity.alerter.AlertDefine;
/**
* Periodic Alert Rule Scheduler
* period alert rule scheduler
*/
@Slf4j
@Component
public class PeriodicAlertRuleScheduler implements CommandLineRunner {
private final PeriodicAlertCalculator calculator;
private final AlertDefineDao alertDefineDao;
private final ScheduledExecutorService scheduledExecutor;
private final Map<Long, ScheduledFuture<?>> scheduledFutures;
public PeriodicAlertRuleScheduler(PeriodicAlertCalculator calculator, AlertDefineDao alertDefineDao) {
this.calculator = calculator;
this.alertDefineDao = alertDefineDao;
ThreadFactory threadFactory = new ThreadFactoryBuilder()
.setUncaughtExceptionHandler((thread, throwable) -> {
log.error("Scheduled periodic alert threshold has uncaughtException.");
log.error(throwable.getMessage(), throwable);
})
.setDaemon(true)
.setNameFormat("periodic-alert-threshold-worker-%d")
.build();
this.scheduledExecutor = Executors.newScheduledThreadPool(10, threadFactory);
this.scheduledFutures = new ConcurrentHashMap<>();
}
public void cancelSchedule(Long ruleId) {
if (ruleId == null) {
return;
}
ScheduledFuture<?> future = scheduledFutures.get(ruleId);
if (future != null) {
future.cancel(true);
scheduledFutures.remove(ruleId);
}
}
public void updateSchedule(AlertDefine rule) {
if (rule == null || rule.getId() == null) {
log.error("Alert rule is null or rule id is null.");
return;
}
cancelSchedule(rule.getId());
if (rule.getType().equals(ALERT_THRESHOLD_TYPE_PERIODIC)) {
ScheduledFuture<?> future = scheduledExecutor.scheduleAtFixedRate(() -> {
calculator.calculate(rule);
}, 0, rule.getPeriod(), java.util.concurrent.TimeUnit.SECONDS);
scheduledFutures.put(rule.getId(), future);
}
}
@Override
public void run(String... args) throws Exception {
log.info("Starting periodic alert rule scheduler...");
List<AlertDefine> periodicRules = alertDefineDao.findAlertDefinesByTypeAndEnableTrue(ALERT_THRESHOLD_TYPE_PERIODIC);
for (AlertDefine rule : periodicRules) {
updateSchedule(rule);
}
}
public class PeriodicAlertRuleScheduler {
// todo implement the following logic
}
@@ -1,63 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.hertzbeat.alert.config;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* SSE manager for alert
*/
@Component
public class AlertSseManager {
private final Map<Long, SseEmitter> emitters = new ConcurrentHashMap<>();
public SseEmitter createEmitter(Long clientId) {
SseEmitter emitter = new SseEmitter(Long.MAX_VALUE);
emitter.onCompletion(() -> removeEmitter(clientId));
emitter.onTimeout(() -> removeEmitter(clientId));
emitters.put(clientId, emitter);
return emitter;
}
@Async
public void broadcast(String data) {
emitters.forEach((clientId, emitter) -> {
try {
emitter.send(SseEmitter.event()
.id(String.valueOf(System.currentTimeMillis()))
.name("ALERT_EVENT")
.data(data));
} catch (IOException e) {
emitter.complete();
removeEmitter(clientId);
}
});
}
private void removeEmitter(Long clientId) {
emitters.remove(clientId);
}
}
@@ -38,9 +38,9 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* Alarm Inhibit management API
* Alarm Silence management API
*/
@Tag(name = "Alert Inhibit API")
@Tag(name = "Alert Silence API")
@RestController
@RequestMapping(path = "/api/alert/inhibit", produces = {APPLICATION_JSON_VALUE})
public class AlertInhibitController {
@@ -49,7 +49,7 @@ public class AlertInhibitController {
private AlertInhibitService alertInhibitService;
@PostMapping
@Operation(summary = "New Alarm Inhibit", description = "Added an alarm Inhibit")
@Operation(summary = "New Alarm Silence", description = "Added an alarm Silence")
public ResponseEntity<Message<Void>> addNewAlertInhibit(@Valid @RequestBody AlertInhibit alertInhibit) {
alertInhibitService.validate(alertInhibit, false);
alertInhibitService.addAlertInhibit(alertInhibit);
@@ -57,7 +57,7 @@ public class AlertInhibitController {
}
@PutMapping
@Operation(summary = "Modifying an Alarm Inhibit", description = "Modify an existing alarm Inhibit")
@Operation(summary = "Modifying an Alarm Silence", description = "Modify an existing alarm Silence")
public ResponseEntity<Message<Void>> modifyAlertInhibit(@Valid @RequestBody AlertInhibit alertInhibit) {
alertInhibitService.validate(alertInhibit, true);
alertInhibitService.modifyAlertInhibit(alertInhibit);
@@ -65,10 +65,10 @@ public class AlertInhibitController {
}
@GetMapping(path = "/{id}")
@Operation(summary = "Querying Alarm Inhibit",
description = "You can obtain alarm Inhibit information based on the alarm Inhibit ID")
@Operation(summary = "Querying Alarm Silence",
description = "You can obtain alarm Silence information based on the alarm Silence ID")
public ResponseEntity<Message<AlertInhibit>> getAlertInhibit(
@Parameter(description = "Alarm Inhibit ID", example = "6565463543") @PathVariable("id") long id) {
@Parameter(description = "Alarm Silence ID", example = "6565463543") @PathVariable("id") long id) {
AlertInhibit alertInhibit = alertInhibitService.getAlertInhibit(id);
return Objects.isNull(alertInhibit)
@@ -36,9 +36,9 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* Inhibit the batch API for alarms
* Silence the batch API for alarms
*/
@Tag(name = "Alert Inhibit Batch API")
@Tag(name = "Alert Silence Batch API")
@RestController
@RequestMapping(path = "/api/alert/inhibits", produces = {APPLICATION_JSON_VALUE})
public class AlertInhibitsController {
@@ -50,7 +50,7 @@ public class AlertInhibitsController {
@Operation(summary = "Query the alarm inhibit list",
description = "You can obtain the list of alarm inhibit by querying filter items")
public ResponseEntity<Message<Page<AlertInhibit>>> getAlertInhibits(
@Parameter(description = "Alarm Inhibit ID", example = "6565463543") @RequestParam(required = false) List<Long> ids,
@Parameter(description = "Alarm Silence ID", example = "6565463543") @RequestParam(required = false) List<Long> ids,
@Parameter(description = "Search Name", example = "x") @RequestParam(required = false) String search,
@Parameter(description = "Sort field, default id", example = "id") @RequestParam(defaultValue = "id") String sort,
@Parameter(description = "Sort mode: asc: ascending, desc: descending", example = "desc") @RequestParam(defaultValue = "desc") String order,
@@ -64,7 +64,7 @@ public class AlertInhibitsController {
@Operation(summary = "Delete alarm inhibit in batches",
description = "Delete alarm inhibit in batches based on the alarm inhibit ID list")
public ResponseEntity<Message<Void>> deleteAlertDefines(
@Parameter(description = "Alarm Inhibit IDs", example = "6565463543") @RequestParam(required = false) List<Long> ids
@Parameter(description = "Alarm Silence IDs", example = "6565463543") @RequestParam(required = false) List<Long> ids
) {
if (ids != null && !ids.isEmpty()) {
alertInhibitService.deleteAlertInhibits(new HashSet<>(ids));
@@ -1,48 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.hertzbeat.alert.controller;
import static org.springframework.http.MediaType.TEXT_EVENT_STREAM_VALUE;
import org.apache.hertzbeat.alert.config.AlertSseManager;
import org.apache.hertzbeat.common.util.SnowFlakeIdGenerator;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
/**
* SSE controller for alert
*/
@RestController
@RequestMapping(path = "/api/alert/sse", produces = {TEXT_EVENT_STREAM_VALUE})
public class AlertSseController {
private final AlertSseManager emitterManager;
public AlertSseController(AlertSseManager emitterManager) {
this.emitterManager = emitterManager;
}
@GetMapping(path = "/subscribe")
public SseEmitter subscribe() {
Long clientId = SnowFlakeIdGenerator.generateId();
return emitterManager.createEmitter(clientId);
}
}
@@ -31,7 +31,6 @@ import org.apache.hertzbeat.common.entity.alerter.NoticeRule;
import org.apache.hertzbeat.common.entity.alerter.NoticeTemplate;
import org.apache.hertzbeat.alert.service.NoticeConfigService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
@@ -83,18 +82,9 @@ public class NoticeConfigController {
@GetMapping(path = "/receivers")
@Operation(summary = "Get a list of message notification recipients based on query filter items",
description = "Get a list of message notification recipients based on query filter items")
public ResponseEntity<Message<Page<NoticeReceiver>>> getReceivers(
@Parameter(description = "en: Recipient name,support fuzzy query", example = "tom") @RequestParam(required = false) final String name,
@Parameter(description = "en: List current page", example = "0") @RequestParam(defaultValue = "0") final int pageIndex,
@Parameter(description = "en: Number of list pages", example = "8") @RequestParam(defaultValue = "8") final int pageSize) {
return ResponseEntity.ok(Message.success(noticeConfigService.getNoticeReceivers(name, pageIndex, pageSize)));
}
@GetMapping(path = "/receivers/all")
@Operation(summary = "Get a list of all message notification recipients",
description = "Get a list of all message notification recipients")
public ResponseEntity<Message<List<NoticeReceiver>>> getAllReceivers() {
return ResponseEntity.ok(Message.success(noticeConfigService.getAllNoticeReceivers()));
public ResponseEntity<Message<List<NoticeReceiver>>> getReceivers(
@Parameter(description = "en: Recipient name,support fuzzy query", example = "tom") @RequestParam(required = false) final String name) {
return ResponseEntity.ok(Message.success(noticeConfigService.getNoticeReceivers(name)));
}
@GetMapping(path = "/receiver/{id}")
@@ -139,11 +129,9 @@ public class NoticeConfigController {
@GetMapping(path = "/rules")
@Operation(summary = "Get a list of message notification policies based on query filter items",
description = "Get a list of message notification policies based on query filter items")
public ResponseEntity<Message<Page<NoticeRule>>> getRules(
@Parameter(description = "en: Recipient name", example = "rule1") @RequestParam(required = false) final String name,
@Parameter(description = "en: List current page", example = "0") @RequestParam(defaultValue = "0") final int pageIndex,
@Parameter(description = "en: Number of list pages", example = "8") @RequestParam(defaultValue = "8") final int pageSize) {
return ResponseEntity.ok(Message.success(noticeConfigService.getNoticeRules(name, pageIndex, pageSize)));
public ResponseEntity<Message<List<NoticeRule>>> getRules(
@Parameter(description = "en: Recipient name", example = "rule1") @RequestParam(required = false) final String name) {
return ResponseEntity.ok(Message.success(noticeConfigService.getNoticeRules(name)));
}
@GetMapping(path = "/rule/{id}")
@@ -188,22 +176,12 @@ public class NoticeConfigController {
@GetMapping(path = "/templates")
@Operation(summary = "Get a list of message notification templates based on query filter items",
description = "Get a list of message notification templates based on query filter items")
public ResponseEntity<Message<Page<NoticeTemplate>>> getTemplates(
@Parameter(description = "Template name,support fuzzy query", example = "rule1") @RequestParam(required = false) final String name,
@Parameter(description = "Whether it is a preset template", example = "true") @RequestParam(defaultValue = "true") final boolean preset,
@Parameter(description = "List current page", example = "0") @RequestParam(defaultValue = "0") final int pageIndex,
@Parameter(description = "Number of list pages", example = "8") @RequestParam(defaultValue = "8") final int pageSize) {
Page<NoticeTemplate> templatePage = noticeConfigService.getNoticeTemplates(name, preset, pageIndex, pageSize);
public ResponseEntity<Message<List<NoticeTemplate>>> getTemplates(
@Parameter(description = "Template name,support fuzzy query", example = "rule1") @RequestParam(required = false) final String name) {
List<NoticeTemplate> templatePage = noticeConfigService.getNoticeTemplates(name);
return ResponseEntity.ok(Message.success(templatePage));
}
@GetMapping(path = "/templates/all")
@Operation(summary = "Get a list of all message notification templates",
description = "Get a list of all message notification templates")
public ResponseEntity<Message<List<NoticeTemplate>>> getAllTemplates() {
return ResponseEntity.ok(Message.success(noticeConfigService.getAllNoticeTemplates()));
}
@GetMapping(path = "/template/{id}")
@Operation(summary = "Get the notification template information based on the template ID",
description = "Get the notification template information based on the template ID")
@@ -23,13 +23,11 @@ import java.util.Map;
import java.util.Optional;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.alert.AlerterWorkerPool;
import org.apache.hertzbeat.alert.config.AlertSseManager;
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
import org.apache.hertzbeat.common.entity.alerter.NoticeReceiver;
import org.apache.hertzbeat.common.entity.alerter.NoticeRule;
import org.apache.hertzbeat.common.entity.alerter.NoticeTemplate;
import org.apache.hertzbeat.alert.service.NoticeConfigService;
import org.apache.hertzbeat.common.util.JsonUtil;
import org.apache.hertzbeat.plugin.PostAlertPlugin;
import org.apache.hertzbeat.plugin.Plugin;
import org.apache.hertzbeat.plugin.runner.PluginRunner;
@@ -47,18 +45,16 @@ public class AlertNoticeDispatch {
private final AlertStoreHandler alertStoreHandler;
private final Map<Byte, AlertNotifyHandler> alertNotifyHandlerMap;
private final PluginRunner pluginRunner;
private final AlertSseManager emitterManager;
public AlertNoticeDispatch(AlerterWorkerPool workerPool,
NoticeConfigService noticeConfigService,
AlertStoreHandler alertStoreHandler,
List<AlertNotifyHandler> alertNotifyHandlerList, PluginRunner pluginRunner, AlertSseManager emitterManager) {
List<AlertNotifyHandler> alertNotifyHandlerList, PluginRunner pluginRunner) {
this.workerPool = workerPool;
this.noticeConfigService = noticeConfigService;
this.alertStoreHandler = alertStoreHandler;
this.pluginRunner = pluginRunner;
alertNotifyHandlerMap = Maps.newHashMapWithExpectedSize(alertNotifyHandlerList.size());
this.emitterManager = emitterManager;
alertNotifyHandlerList.forEach(r -> alertNotifyHandlerMap.put(r.type(), r));
}
@@ -108,27 +104,27 @@ public class AlertNoticeDispatch {
public void dispatchAlarm(GroupAlert groupAlert) {
if (groupAlert != null) {
// Determining alarm type storage
GroupAlert storedGroupAlert = alertStoreHandler.store(groupAlert);
alertStoreHandler.store(groupAlert);
// Notice distribution
sendNotify(storedGroupAlert);
sendNotify(groupAlert);
// Execute the plugin if enable (Compatible with old version plugins, will be removed in later versions)
pluginRunner.pluginExecute(Plugin.class, plugin -> plugin.alert(storedGroupAlert));
pluginRunner.pluginExecute(Plugin.class, plugin -> plugin.alert(groupAlert));
// Execute the plugin if enable with params
pluginRunner.pluginExecute(PostAlertPlugin.class, (afterAlertPlugin, pluginContext) -> afterAlertPlugin.execute(storedGroupAlert, pluginContext));
// Send alert to the sse client
emitterManager.broadcast(JsonUtil.toJson(storedGroupAlert));
pluginRunner.pluginExecute(PostAlertPlugin.class, (afterAlertPlugin, pluginContext) -> afterAlertPlugin.execute(groupAlert, pluginContext));
}
}
private void sendNotify(GroupAlert alert) {
matchNoticeRulesByAlert(alert).ifPresent(noticeRules -> noticeRules.forEach(rule -> workerPool.executeNotify(() -> rule.getReceiverId()
.forEach(receiverId -> {
try {
sendNoticeMsg(getOneReceiverById(receiverId),
getOneTemplateById(rule.getTemplateId()), alert);
} catch (AlertNoticeException e) {
log.warn("DispatchTask sendNoticeMsg error, message: {}", e.getMessage());
}
}))));
matchNoticeRulesByAlert(alert).ifPresent(noticeRules -> noticeRules.forEach(rule -> {
workerPool.executeNotify(() -> rule.getReceiverId()
.forEach(receiverId -> {
try {
sendNoticeMsg(getOneReceiverById(receiverId),
getOneTemplateById(rule.getTemplateId()), alert);
} catch (AlertNoticeException e) {
log.warn("DispatchTask sendNoticeMsg error, message: {}", e.getMessage());
}
}));
}));
}
}
@@ -28,9 +28,7 @@ public interface AlertStoreHandler {
* Persistent alarm records
* It is necessary to associate and assign values
* to the alert tag information tags while persisting.
*
* @param alert alarm information
* @return groupAlert
*/
GroupAlert store(GroupAlert alert);
void store(GroupAlert alert);
}
@@ -17,7 +17,6 @@
package org.apache.hertzbeat.alert.notice.impl;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -47,53 +46,44 @@ final class DbAlertStoreHandlerImpl implements AlertStoreHandler {
private final SingleAlertDao singleAlertDao;
@Override
public GroupAlert store(GroupAlert groupAlert) {
public void store(GroupAlert groupAlert) {
if (groupAlert == null || groupAlert.getAlerts() == null || groupAlert.getAlerts().isEmpty()) {
log.error("The Group Alerts is empty, ignore store");
return groupAlert;
return;
}
// 1. Find existing alert group
GroupAlert existGroupAlert = groupAlertDao.findByGroupKey(groupAlert.getGroupKey());
// 2. Process individual alerts
Set<String> alertFingerprints = new HashSet<>(8);
List<SingleAlert> originalAlerts = groupAlert.getAlerts();
List<SingleAlert> newAlerts = new ArrayList<>();
for (SingleAlert singleAlert : originalAlerts) {
groupAlert.getAlerts().forEach(singleAlert -> {
SingleAlert existAlert = singleAlertDao.findByFingerprint(singleAlert.getFingerprint());
if (existAlert != null) {
// Update the existing alert with the ID and creation time from the database
// Update existing alert
singleAlert.setId(existAlert.getId());
singleAlert.setGmtCreate(existAlert.getGmtCreate());
// Status transition logic
if (CommonConstants.ALERT_STATUS_FIRING.equals(singleAlert.getStatus())) {
// If the alert is firing and the existing alert is not resolved, update the start time and trigger times
if (!CommonConstants.ALERT_STATUS_RESOLVED.equals(existAlert.getStatus())) {
singleAlert.setStartAt(existAlert.getStartAt());
int triggerTimes = Optional.ofNullable(existAlert.getTriggerTimes()).orElse(1)
+ Optional.ofNullable(singleAlert.getTriggerTimes()).orElse(1);
int triggerTimes = Optional.ofNullable(existAlert.getTriggerTimes()).orElse(1) + Optional.ofNullable(singleAlert.getTriggerTimes()).orElse(1);
singleAlert.setTriggerTimes(triggerTimes);
}
}
} else if (CommonConstants.ALERT_STATUS_RESOLVED.equals(singleAlert.getStatus())) {
// If the alert is resolved, set the end time (if not already set) and copy other fields from the existing alert
// Transition to resolved state
if (singleAlert.getEndAt() == null) {
singleAlert.setEndAt(System.currentTimeMillis());
singleAlert.setEndAt(System.currentTimeMillis());
}
singleAlert.setStartAt(existAlert.getStartAt());
singleAlert.setActiveAt(existAlert.getActiveAt());
singleAlert.setTriggerTimes(existAlert.getTriggerTimes());
}
}
SingleAlert savedSingleAlert = singleAlertDao.save(singleAlert);
newAlerts.add(savedSingleAlert);
alertFingerprints.add(savedSingleAlert.getFingerprint());
}
groupAlert.setAlerts(newAlerts);
alertFingerprints.add(singleAlert.getFingerprint());
singleAlertDao.save(singleAlert);
});
// 3. Process resolved alerts
if (existGroupAlert != null) {
List<String> existFingerprints = existGroupAlert.getAlertFingerprints();
@@ -130,8 +120,6 @@ final class DbAlertStoreHandlerImpl implements AlertStoreHandler {
// 4. Save alert group
groupAlert.setAlertFingerprints(alertFingerprints.stream().toList());
GroupAlert savedGroupAlert = groupAlertDao.save(groupAlert);
savedGroupAlert.setAlerts(groupAlert.getAlerts());
return savedGroupAlert;
groupAlertDao.save(groupAlert);
}
}
@@ -21,8 +21,8 @@ package org.apache.hertzbeat.alert.reduce;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@@ -267,7 +267,7 @@ public class AlarmGroupReduce {
.groupLabels(alert.getLabels())
.commonLabels(alert.getLabels())
.commonAnnotations(alert.getAnnotations())
.alerts(new LinkedList<>(List.of(alert)))
.alerts(Collections.singletonList(alert))
.status(alert.getStatus())
.build();
@@ -26,10 +26,10 @@ import java.util.Map;
public interface DataSourceService {
/**
* execute query expr calculate
* execute query
* @param datasource datasource
* @param expr query expr
* @param query query
* @return result
*/
List<Map<String, Object>> calculate(String datasource, String expr);
List<Map<String, Object>> query(String datasource, String query);
}
@@ -23,7 +23,6 @@ import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
import org.apache.hertzbeat.common.entity.alerter.NoticeReceiver;
import org.apache.hertzbeat.common.entity.alerter.NoticeRule;
import org.apache.hertzbeat.common.entity.alerter.NoticeTemplate;
import org.springframework.data.domain.Page;
/**
* Message notification configuration interface
@@ -33,32 +32,23 @@ public interface NoticeConfigService {
/**
* Dynamic conditional query
* @param name Recipient name,support fuzzy query
* @param pageIndex Page number
* @param pageSize Number of records per page
* @return Search result
*/
Page<NoticeReceiver> getNoticeReceivers(String name, int pageIndex, int pageSize);
List<NoticeReceiver> getNoticeReceivers(String name);
/**
* Dynamic conditional query
* @param name Template name,support fuzzy query
* @param preset Whether it is a system preset template
* true: System preset template
* false: Custom template
* @param pageIndex Page number
* @param pageSize Number of records per page
* @return Search result
*/
Page<NoticeTemplate> getNoticeTemplates(String name, boolean preset, int pageIndex, int pageSize);
List<NoticeTemplate> getNoticeTemplates(String name);
/**
* Dynamic conditional query
* @param name Recipient name ,support fuzzy query
* @param pageIndex Page number
* @param pageSize Number of records per page
* @param name Recipient name
* @return Search result
*/
Page<NoticeRule> getNoticeRules(String name, int pageIndex, int pageSize);
List<NoticeRule> getNoticeRules(String name);
/**
* Add a notification recipient
@@ -164,15 +154,4 @@ public interface NoticeConfigService {
*/
boolean sendTestMsg(NoticeReceiver noticeReceiver);
/**
* Query all notification recipients
* @return Recipient List
*/
List<NoticeReceiver> getAllNoticeReceivers();
/**
* Query all notification policies
* @return Notification Policy List
*/
List<NoticeTemplate> getAllNoticeTemplates();
}
@@ -25,7 +25,6 @@ import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.Predicate;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.alert.calculate.PeriodicAlertRuleScheduler;
import org.apache.hertzbeat.alert.dao.AlertDefineDao;
import org.apache.hertzbeat.alert.service.AlertDefineImExportService;
import org.apache.hertzbeat.alert.service.AlertDefineService;
@@ -68,9 +67,6 @@ public class AlertDefineServiceImpl implements AlertDefineService {
@Autowired
private AlertDefineDao alertDefineDao;
@Autowired
private PeriodicAlertRuleScheduler periodicAlertRuleScheduler;
private final Map<String, AlertDefineImExportService> alertDefineImExportServiceMap = new HashMap<>();
@@ -102,22 +98,19 @@ public class AlertDefineServiceImpl implements AlertDefineService {
@Override
public void addAlertDefine(AlertDefine alertDefine) throws RuntimeException {
alertDefine = alertDefineDao.save(alertDefine);
periodicAlertRuleScheduler.updateSchedule(alertDefine);
alertDefineDao.save(alertDefine);
CacheFactory.clearAlertDefineCache();
}
@Override
public void modifyAlertDefine(AlertDefine alertDefine) throws RuntimeException {
alertDefineDao.save(alertDefine);
periodicAlertRuleScheduler.updateSchedule(alertDefine);
CacheFactory.clearAlertDefineCache();
}
@Override
public void deleteAlertDefine(long alertId) throws RuntimeException {
alertDefineDao.deleteById(alertId);
periodicAlertRuleScheduler.cancelSchedule(alertId);
CacheFactory.clearAlertDefineCache();
}
@@ -130,9 +123,6 @@ public class AlertDefineServiceImpl implements AlertDefineService {
@Override
public void deleteAlertDefines(Set<Long> alertIds) throws RuntimeException {
alertDefineDao.deleteAlertDefinesByIdIn(alertIds);
for (Long alertId : alertIds) {
periodicAlertRuleScheduler.cancelSchedule(alertId);
}
CacheFactory.clearAlertDefineCache();
}
@@ -17,21 +17,13 @@
package org.apache.hertzbeat.alert.service.impl;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Stack;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.alert.service.DataSourceService;
import org.apache.hertzbeat.warehouse.db.QueryExecutor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.springframework.util.StringUtils;
/**
* datasource service
@@ -41,307 +33,21 @@ import org.springframework.util.StringUtils;
public class DataSourceServiceImpl implements DataSourceService {
@Autowired(required = false)
private List<QueryExecutor> executors;
private static final Pattern EXPR_TOKEN = Pattern.compile("\\(|\\)|[a-zA-Z_][a-zA-Z0-9_={}\\[\\]\".]*|\\d+(\\.\\d+)?|>=|<=|==|!=|>|<|and|or|unless");
private static final String THRESHOLD = "__threshold__";
private static final String VALUE = "__value__";
private Map<String, QueryExecutor> executors;
@Override
public List<Map<String, Object>> calculate(String datasource, String expr) {
if (!StringUtils.hasText(expr)) {
throw new IllegalArgumentException("Empty expression");
}
if (executors == null || executors.isEmpty()) {
throw new IllegalArgumentException("No query executor found");
}
QueryExecutor executor = executors.stream().filter(e -> e.support(datasource)).findFirst().orElse(null);
public List<Map<String, Object>> query(String datasource, String query) {
QueryExecutor executor = executors.get(datasource);
if (executor == null) {
throw new IllegalArgumentException("Unsupported datasource: " + datasource);
}
// replace all white space
expr = expr.replaceAll("\\s+", " ");
try {
return evaluate(expr, executor);
} catch (Exception e) {
log.error("Error executing query on datasource {}: {}", datasource, e.getMessage());
throw new RuntimeException("Query execution failed", e);
}
return executor.execute(query);
}
private List<Map<String, Object>> evaluate(String expr, QueryExecutor executor) {
Stack<List<Map<String, Object>>> values = new Stack<>();
Stack<String> operators = new Stack<>();
Matcher matcher = EXPR_TOKEN.matcher(expr);
List<String> tokens = new ArrayList<>();
while (matcher.find()) {
tokens.add(matcher.group());
}
for (String token : tokens) {
if (token.equals("(")) {
operators.push(token);
} else if (token.equals(")")) {
while (!operators.isEmpty() && !operators.peek().equals("(")) {
applyOperator(values, operators.pop());
}
// remove the left parenthesis
operators.pop();
} else if (token.matches(">=|<=|==|!=|>|<")) {
operators.push(token);
} else if (token.equals("and") || token.equals("or") || token.equals("unless")) {
while (!operators.isEmpty() && precedence(operators.peek()) >= precedence(token)) {
applyOperator(values, operators.pop());
}
operators.push(token);
} else if (token.matches("\\d+(\\.\\d+)?")) {
double value = Double.parseDouble(token);
List<Map<String, Object>> numAsList = new ArrayList<>();
numAsList.add(Map.of(THRESHOLD, value));
values.push(numAsList);
} else if (token.matches("[a-zA-Z_][a-zA-Z0-9_={}\\[\\]\".]*")) {
List<Map<String, Object>> results = executor.execute(token);
values.push(results);
}
}
while (!operators.isEmpty()) {
applyOperator(values, operators.pop());
}
return values.isEmpty() ? new LinkedList<>() : values.pop();
}
private int precedence(String op) {
return switch (op) {
case "or" -> 1;
case "unless" -> 2;
case "and" -> 3;
case ">", "<", ">=", "<=", "==", "!=" -> 4;
default -> 0;
};
}
private void applyOperator(Stack<List<Map<String, Object>>> values, String op) {
if (values.size() < 2) {
return;
};
List<Map<String, Object>> rightOperand = values.pop();
List<Map<String, Object>> leftOperand = values.pop();
if (rightOperand.size() == 1 && rightOperand.get(0).containsKey(THRESHOLD)) {
double threshold = (double) rightOperand.get(0).get(THRESHOLD);
List<Map<String, Object>> result = new ArrayList<>();
for (Map<String, Object> item : leftOperand) {
Object queryValues = item.get(VALUE);
if (queryValues == null) {
// ignore the query result data is empty
continue;
}
// queryValues may be a list of values, or a single value
Object matchValue = evaluateCondition(queryValues, op, threshold);
item.put(VALUE, matchValue);
// if matchValue is null, mean not match the threshold
// if not null, mean match the threshold
result.add(new HashMap<>(item));
}
if (!result.isEmpty()) {
values.push(result);
}
return;
}
Map<String, Object> leftMap = null;
boolean leftMatch = false;
Map<String, Object> rightMap = null;
boolean rightMatch = false;
switch (op) {
case "and" -> {
for (Map<String, Object> item : leftOperand) {
if (leftMap == null) {
leftMap = item;
}
if (item.get(VALUE) != null) {
leftMap = item;
leftMatch = true;
break;
}
}
for (Map<String, Object> item : rightOperand) {
if (rightMap == null) {
rightMap = item;
}
if (item.get(VALUE) != null) {
rightMap = item;
rightMatch = true;
break;
}
}
if (leftMatch && rightMatch) {
rightMap.putAll(leftMap);
values.push(new LinkedList<>(List.of(rightMap)));
} else if (leftMap != null) {
leftMap.put(VALUE, null);
values.push(new LinkedList<>(List.of(leftMap)));
} else if (rightMap != null) {
rightMap.put(VALUE, null);
values.push(new LinkedList<>(List.of(rightMap)));
}
}
case "or" -> {
for (Map<String, Object> item : leftOperand) {
if (leftMap == null) {
leftMap = item;
}
if (item.get(VALUE) != null) {
leftMap = item;
leftMatch = true;
break;
}
}
for (Map<String, Object> item : rightOperand) {
if (rightMap == null) {
rightMap = item;
}
if (item.get(VALUE) != null) {
rightMap = item;
rightMatch = true;
break;
}
}
if (leftMatch && rightMatch) {
rightMap.putAll(leftMap);
values.push(new LinkedList<>(List.of(rightMap)));
} else if (leftMatch) {
values.push(new LinkedList<>(List.of(leftMap)));
} else if (rightMatch) {
values.push(new LinkedList<>(List.of(rightMap)));
} else {
if (leftMap != null && rightMap != null) {
rightMap.putAll(leftMap);
values.push(new LinkedList<>(List.of(rightMap)));
} else if (leftMap != null) {
values.push(new LinkedList<>(List.of(leftMap)));
} else if (rightMap != null){
values.push(new LinkedList<>(List.of(rightMap)));
}
}
}
case "unless" -> {
for (Map<String, Object> item : leftOperand) {
if (leftMap == null) {
leftMap = item;
}
if (item.get(VALUE) != null) {
leftMap = item;
leftMatch = true;
break;
}
}
for (Map<String, Object> item : rightOperand) {
if (rightMap == null) {
rightMap = item;
}
if (item.get(VALUE) != null) {
rightMap = item;
rightMatch = true;
break;
}
}
if (leftMatch && !rightMatch) {
values.push(new LinkedList<>(List.of(leftMap)));
} else {
if (leftMap != null) {
leftMap.put(VALUE, null);
values.push(new LinkedList<>(List.of(leftMap)));
} else {
if (rightMap != null) {
rightMap.put(VALUE, null);
values.push(new LinkedList<>(List.of(rightMap)));
}
}
}
}
default -> throw new IllegalArgumentException("Unsupported operator: " + op);
}
}
private Object evaluateCondition(Object value, String operator, Double threshold) {
// value may be a list of values, or a single value
switch (operator) {
case ">":
// if value is list, return the max value
if (value instanceof List<?> values) {
Double doubleValue = values.stream().map(v -> Double.valueOf(v.toString()))
.max(Double::compareTo).orElse(null);
if (doubleValue != null) {
return doubleValue > threshold ? doubleValue : null;
} else {
return null;
}
} else {
return Double.parseDouble(value.toString()) > threshold ? value : null;
}
case ">=":
if (value instanceof List<?> values) {
Double doubleValue = values.stream().map(v -> Double.valueOf(v.toString()))
.max(Double::compareTo).orElse(null);
if (doubleValue != null) {
return doubleValue >= threshold ? doubleValue : null;
} else {
return null;
}
} else {
return Double.parseDouble(value.toString()) >= threshold ? value : null;
}
case "<":
if (value instanceof List<?> values) {
Double doubleValue = values.stream().map(v -> Double.valueOf(v.toString()))
.min(Double::compareTo).orElse(null);
if (doubleValue != null) {
return doubleValue < threshold ? doubleValue : null;
} else {
return null;
}
} else {
return Double.parseDouble(value.toString()) < threshold ? value : null;
}
case "<=":
if (value instanceof List<?> values) {
Double doubleValue = values.stream().map(v -> Double.valueOf(v.toString()))
.min(Double::compareTo).orElse(null);
if (doubleValue != null) {
return doubleValue <= threshold ? doubleValue : null;
} else {
return null;
}
} else {
return Double.parseDouble(value.toString()) <= threshold ? value : null;
}
case "==":
if (value instanceof List<?> values) {
for (Object v : values) {
if (v.equals(threshold)) {
return v;
}
}
return null;
} else {
return value.equals(threshold) ? value : null;
}
case "!=":
if (value instanceof List<?> values) {
for (Object v : values) {
if (v.equals(threshold)) {
return null;
}
}
return value;
} else {
return value.equals(threshold) ? null : value;
}
default:
// unsupported operator todo add more operator
return null;
}
}
public void setExecutors(List<QueryExecutor> mockExecutor) {
this.executors = mockExecutor;
/**
*
*/
public interface QueryExecutor {
List<Map<String, Object>> execute(String query);
}
}
@@ -18,6 +18,18 @@
package org.apache.hertzbeat.alert.service.impl;
import jakarta.persistence.criteria.Predicate;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.hertzbeat.common.cache.CacheFactory;
@@ -39,28 +51,10 @@ import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
/**
* Message notification configuration implementation
*/
@@ -86,87 +80,44 @@ public class NoticeConfigServiceImpl implements NoticeConfigService, CommandLine
private AlertNoticeDispatch dispatcherAlarm;
@Override
public Page<NoticeReceiver> getNoticeReceivers(String name, int pageIndex, int pageSize) {
public List<NoticeReceiver> getNoticeReceivers(String name) {
Specification<NoticeReceiver> specification = (root, query, criteriaBuilder) -> {
Predicate predicate = criteriaBuilder.conjunction();
if (StringUtils.isNotBlank(name)) {
Predicate predicateName = criteriaBuilder.like(
criteriaBuilder.lower(root.get("name")), "%" + name.toLowerCase() + "%"
);
Predicate predicateName = criteriaBuilder.like(root.get("name"), "%" + name + "%");
predicate = criteriaBuilder.and(predicateName);
}
return predicate;
};
return noticeReceiverDao.findAll(specification, PageRequest.of(pageIndex, pageSize, Sort.by(Sort.Direction.DESC, "id")));
return noticeReceiverDao.findAll(specification);
}
@Override
public List<NoticeReceiver> getAllNoticeReceivers() {
return noticeReceiverDao.findAll();
}
@Override
public Page<NoticeTemplate> getNoticeTemplates(String name, boolean preset, int pageIndex, int pageSize) {
if (preset) {
// Query preset templates
List<NoticeTemplate> defaultTemplates = new LinkedList<>(PRESET_TEMPLATE.values());
// Filter by name (case-insensitive)
List<NoticeTemplate> filteredDefaultTemplates = defaultTemplates.stream()
.filter(template -> StringUtils.isBlank(name)
|| template.getName().toLowerCase().contains(name.toLowerCase()))
.collect(Collectors.toList());
// Pagination logic
int totalItems = filteredDefaultTemplates.size();
int fromIndex = Math.min(pageIndex * pageSize, totalItems);
int toIndex = Math.min(fromIndex + pageSize, totalItems);
if (fromIndex >= totalItems) {
return new PageImpl<>(Collections.emptyList(), PageRequest.of(pageIndex, pageSize), totalItems);
public List<NoticeTemplate> getNoticeTemplates(String name) {
Specification<NoticeTemplate> specification = (root, query, criteriaBuilder) -> {
Predicate predicate = criteriaBuilder.conjunction();
if (StringUtils.isNotBlank(name)) {
Predicate predicateName = criteriaBuilder.like(root.get("name"), "%" + name + "%");
predicate = criteriaBuilder.and(predicateName);
}
List<NoticeTemplate> paginatedTemplates = filteredDefaultTemplates.subList(fromIndex, toIndex);
return new PageImpl<>(paginatedTemplates, PageRequest.of(pageIndex, pageSize), totalItems);
} else {
// Query custom templates
Specification<NoticeTemplate> specification = (root, query, criteriaBuilder) -> {
Predicate predicate = criteriaBuilder.conjunction();
if (StringUtils.isNotBlank(name)) {
Predicate predicateName = criteriaBuilder.like(
criteriaBuilder.lower(root.get("name")), "%" + name.toLowerCase() + "%"
);
predicate = criteriaBuilder.and(predicateName);
}
return predicate;
};
PageRequest pageRequest = PageRequest.of(pageIndex, pageSize, Sort.by(Sort.Direction.DESC, "id"));
return noticeTemplateDao.findAll(specification, pageRequest);
}
}
@Override
public List<NoticeTemplate> getAllNoticeTemplates() {
return predicate;
};
List<NoticeTemplate> defaultTemplates = new LinkedList<>(PRESET_TEMPLATE.values());
defaultTemplates.addAll(noticeTemplateDao.findAll());
defaultTemplates.addAll(noticeTemplateDao.findAll(specification));
return defaultTemplates;
}
@Override
public Page<NoticeRule> getNoticeRules(String name, int pageIndex, int pageSize) {
public List<NoticeRule> getNoticeRules(String name) {
Specification<NoticeRule> specification = (root, query, criteriaBuilder) -> {
Predicate predicate = criteriaBuilder.conjunction();
if (StringUtils.isNotBlank(name)) {
Predicate predicateName = criteriaBuilder.like(
criteriaBuilder.lower(root.get("name")), "%" + name.toLowerCase() + "%"
);
Predicate predicateName = criteriaBuilder.like(root.get("name"), "%" + name + "%");
predicate = criteriaBuilder.and(predicateName);
}
return predicate;
};
return noticeRuleDao.findAll(specification, PageRequest.of(pageIndex, pageSize, Sort.by(Sort.Direction.DESC, "id")));
return noticeRuleDao.findAll(specification);
}
@Override
@@ -28,6 +28,7 @@ import org.apache.hertzbeat.common.entity.alerter.AlertDefineMonitorBind;
import org.apache.hertzbeat.common.entity.manager.Monitor;
import org.apache.hertzbeat.common.util.JsonUtil;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
@@ -42,6 +43,7 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders;
/**
* Test case for {@link AlertDefineController}
*/
@Disabled
@ExtendWith(MockitoExtension.class)
class AlertDefineControllerTest {
@@ -64,7 +66,6 @@ class AlertDefineControllerTest {
this.alertDefine = AlertDefine.builder()
.id(1L)
.name("alertDefine")
.expr("1 > 0")
.times(1)
.template("template")
@@ -137,4 +138,14 @@ class AlertDefineControllerTest {
.andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
.andReturn();
}
@Test
void applyAlertDefineMonitorsBind() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.post("/api/alert/define/" + this.alertDefine.getId() + "/monitors")
.contentType(MediaType.APPLICATION_JSON)
.content(JsonUtil.toJson(this.alertDefineMonitorBinds)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
.andReturn();
}
}
@@ -19,6 +19,7 @@ package org.apache.hertzbeat.alert.controller;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@@ -30,6 +31,7 @@ import org.apache.hertzbeat.common.constants.CommonConstants;
import org.apache.hertzbeat.common.entity.alerter.AlertDefine;
import org.apache.hertzbeat.common.util.JsonUtil;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
@@ -48,6 +50,7 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders;
* Test case for {@link AlertDefinesController}
* Test whether the data mocked at the mock is correct, and test whether the format of the returned data is correct
*/
@Disabled
@ExtendWith(MockitoExtension.class)
class AlertDefinesControllerTest {
@@ -90,7 +93,8 @@ class AlertDefinesControllerTest {
pageRequest = PageRequest.of((Integer) content.get("pageIndex"), (Integer) content.get("pageSize"), sortExp);
}
@Test
// @Test
// todo: fix this test
void getAlertDefines() throws Exception {
// Test the correctness of the mock
@@ -108,27 +112,31 @@ class AlertDefinesControllerTest {
// }
// }))).thenReturn(new PageImpl<AlertDefine>(new ArrayList<AlertDefine>()));
AlertDefine define = AlertDefine.builder().id(9L).expr("x").times(1).build();
PageRequest pageRequest = PageRequest.of(pageIndex, pageSize, Sort.by(Sort.Order.asc(sort)));
Mockito.when(alertDefineService.getAlertDefines(
Mockito.eq(ids),
Mockito.isNull(),
Mockito.eq(sort),
Mockito.eq(order),
Mockito.eq(pageIndex),
Mockito.eq(pageSize)
)).thenReturn(new PageImpl<>(Collections.singletonList(define), pageRequest, 1));
Mockito.when(alertDefineService.getAlertDefines(null, null, "id", "desc", 1, 10)).thenReturn(new PageImpl<>(Collections.singletonList(define)));
mockMvc.perform(MockMvcRequestBuilders.get("/api/alert/defines")
mockMvc.perform(MockMvcRequestBuilders.get(
"/api/alert/defines")
.param("ids", ids.toString().substring(1, ids.toString().length() - 1))
.param("priority", priority.toString())
.param("sort", sort)
.param("order", order)
.param("pageIndex", String.valueOf(pageIndex))
.param("pageSize", String.valueOf(pageSize)))
.param("pageIndex", pageIndex.toString())
.param("pageSize", pageSize.toString()))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
.andExpect(jsonPath("$.data.content[0].id").value(9))
.andExpect(jsonPath("$.data.content[0].expr").value("x"))
.andExpect(jsonPath("$.data.content[0].times").value(1))
.andExpect(jsonPath("$.data.content").value(new ArrayList<>()))
.andExpect(jsonPath("$.data.pageable").value("INSTANCE"))
.andExpect(jsonPath("$.data.totalPages").value(1))
.andExpect(jsonPath("$.data.totalElements").value(0))
.andExpect(jsonPath("$.data.last").value(true))
.andExpect(jsonPath("$.data.number").value(0))
.andExpect(jsonPath("$.data.size").value(0))
.andExpect(jsonPath("$.data.first").value(true))
.andExpect(jsonPath("$.data.numberOfElements").value(0))
.andExpect(jsonPath("$.data.empty").value(true))
.andExpect(jsonPath("$.data.sort.empty").value(true))
.andExpect(jsonPath("$.data.sort.sorted").value(false))
.andExpect(jsonPath("$.data.sort.unsorted").value(true))
.andReturn();
}
@@ -27,12 +27,12 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standaloneSetup;
import org.apache.hertzbeat.alert.service.AlertGroupConvergeService;
import org.apache.hertzbeat.common.constants.CommonConstants;
import org.apache.hertzbeat.common.entity.alerter.AlertGroupConverge;
import org.apache.hertzbeat.common.util.JsonUtil;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
@@ -44,6 +44,7 @@ import org.springframework.test.web.servlet.MockMvc;
/**
* test case for {@link AlertGroupConvergeController}
*/
@Disabled
@ExtendWith(MockitoExtension.class)
public class AlertGroupConvergeControllerTest {
@@ -78,8 +79,8 @@ public class AlertGroupConvergeControllerTest {
mockMvc.perform(post("/api/alert/group")
.contentType(MediaType.APPLICATION_JSON)
.content(JsonUtil.toJson(alertGroupConverge)))
.andExpect(status().isOk())
.content(JsonUtil.toJson(alertGroupConverge))
).andExpect(status().isOk())
.andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
.andExpect(jsonPath("$.msg").value("Add success"));
}
@@ -92,8 +93,8 @@ public class AlertGroupConvergeControllerTest {
mockMvc.perform(put("/api/alert/group")
.contentType(MediaType.APPLICATION_JSON)
.content(JsonUtil.toJson(alertGroupConverge)))
.andExpect(status().isOk())
.content(JsonUtil.toJson(alertGroupConverge))
).andExpect(status().isOk())
.andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
.andExpect(jsonPath("$.msg").value("Modify success"));
}
@@ -118,6 +119,7 @@ public class AlertGroupConvergeControllerTest {
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value((int) CommonConstants.MONITOR_NOT_EXIST_CODE))
.andExpect(jsonPath("$.msg").value("Alert Group Converge not exist."));
.andExpect(jsonPath("$.msg").value("AlertGroupConverge not exist."));
}
}
@@ -33,6 +33,7 @@ import org.apache.hertzbeat.alert.service.AlertGroupConvergeService;
import org.apache.hertzbeat.common.constants.CommonConstants;
import org.apache.hertzbeat.common.entity.alerter.AlertGroupConverge;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
@@ -51,6 +52,7 @@ import org.springframework.test.web.servlet.MockMvc;
*/
@ExtendWith(MockitoExtension.class)
@Disabled
class AlertGroupConvergesControllerTest {
private MockMvc mockMvc;
@@ -99,7 +101,6 @@ class AlertGroupConvergesControllerTest {
.param("order", "desc")
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
.andExpect(jsonPath("$.data.content[0].id").value(1))
.andExpect(jsonPath("$.data.content[0].name").value("Converge1"))
.andExpect(jsonPath("$.data.content[1].id").value(2))
@@ -117,4 +118,5 @@ class AlertGroupConvergesControllerTest {
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE));
}
}
}
@@ -17,6 +17,7 @@
package org.apache.hertzbeat.alert.controller;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -43,10 +44,6 @@ import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
@@ -171,38 +168,12 @@ class NoticeConfigControllerTest {
@Test
void getReceivers() throws Exception {
NoticeReceiver receiver1 = new NoticeReceiver();
receiver1.setId(1L);
receiver1.setName("Receiver1");
NoticeReceiver receiver2 = new NoticeReceiver();
receiver2.setId(2L);
receiver2.setName("Receiver2");
Page<NoticeReceiver> receiverPage = new PageImpl<>(
Arrays.asList(receiver1, receiver2),
PageRequest.of(0, 8, Sort.by("id").descending()),
2
);
when(noticeConfigService.getNoticeReceivers("Receiver", 0, 8)).thenReturn(receiverPage);
this.mockMvc.perform(MockMvcRequestBuilders.get("/api/notice/receivers")
.param("name", "Receiver")
.param("pageIndex", "0")
.param("pageSize", "8")
.param("sort", "id")
.param("order", "desc")
.accept(MediaType.APPLICATION_JSON))
//Mockito.when(noticeConfigService.getNoticeReceivers())
this.mockMvc.perform(MockMvcRequestBuilders.get("/api/notice/receivers?name={name}", "tom"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.data.content[0].id").value(1))
.andExpect(jsonPath("$.data.content[0].name").value("Receiver1"))
.andExpect(jsonPath("$.data.content[1].id").value(2))
.andExpect(jsonPath("$.data.content[1].name").value("Receiver2"))
.andExpect(jsonPath("$.data.totalElements").value(2))
.andExpect(jsonPath("$.data.totalPages").value(1))
.andExpect(jsonPath("$.data.size").value(8))
.andExpect(jsonPath("$.data.number").value(0));
.andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
.andReturn();
}
@Test
@@ -274,38 +245,15 @@ class NoticeConfigControllerTest {
@Test
void getRules() throws Exception {
NoticeRule rule1 = new NoticeRule();
rule1.setId(1L);
rule1.setName("Rule1");
NoticeRule rule2 = new NoticeRule();
rule2.setId(2L);
rule2.setName("Rule2");
Page<NoticeRule> rulePage = new PageImpl<>(
Arrays.asList(rule1, rule2),
PageRequest.of(0, 8, Sort.by("id").descending()),
2
);
when(noticeConfigService.getNoticeRules("Rule", 0, 8)).thenReturn(rulePage);
this.mockMvc.perform(MockMvcRequestBuilders.get("/api/notice/rules")
.param("name", "Rule")
.param("pageIndex", "0")
.param("pageSize", "8")
.param("sort", "id")
.param("order", "desc")
.accept(MediaType.APPLICATION_JSON))
this.mockMvc.perform(MockMvcRequestBuilders.get("/api/notice/rules"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.data.content[0].id").value(1))
.andExpect(jsonPath("$.data.content[0].name").value("Rule1"))
.andExpect(jsonPath("$.data.content[1].id").value(2))
.andExpect(jsonPath("$.data.content[1].name").value("Rule2"))
.andExpect(jsonPath("$.data.totalElements").value(2))
.andExpect(jsonPath("$.data.totalPages").value(1))
.andExpect(jsonPath("$.data.size").value(8))
.andExpect(jsonPath("$.data.number").value(0));
.andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
.andReturn();
this.mockMvc.perform(MockMvcRequestBuilders.get("/api/notice/rules?name={name}", "tom"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
.andReturn();
}
@Test
@@ -414,40 +362,22 @@ class NoticeConfigControllerTest {
}
@Test
void getTemplates() throws Exception {
void testGetTemplates() throws Exception {
// Mock the service response
NoticeTemplate template1 = new NoticeTemplate();
template1.setId(1L);
template1.setName("Template1");
NoticeTemplate template2 = new NoticeTemplate();
template2.setId(2L);
template2.setName("Template2");
List<NoticeTemplate> templates = Arrays.asList(template1, template2);
when(noticeConfigService.getNoticeTemplates(any())).thenReturn(templates);
Page<NoticeTemplate> templatePage = new PageImpl<>(
Arrays.asList(template1, template2),
PageRequest.of(0, 8, Sort.by("id").descending()),
2
);
when(noticeConfigService.getNoticeTemplates("Template", true, 0, 8)).thenReturn(templatePage);
this.mockMvc.perform(MockMvcRequestBuilders.get("/api/notice/templates")
.param("name", "Template")
.param("preset", "true")
.param("pageIndex", "0")
.param("pageSize", "8")
.param("sort", "id")
.param("order", "desc")
.accept(MediaType.APPLICATION_JSON))
// Perform the GET request and verify the response
this.mockMvc.perform(get("/api/notice/templates")
.param("name", "Template"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.data.content[0].id").value(1))
.andExpect(jsonPath("$.data.content[0].name").value("Template1"))
.andExpect(jsonPath("$.data.content[1].id").value(2))
.andExpect(jsonPath("$.data.content[1].name").value("Template2"))
.andExpect(jsonPath("$.data.totalElements").value(2))
.andExpect(jsonPath("$.data.totalPages").value(1))
.andExpect(jsonPath("$.data.size").value(8))
.andExpect(jsonPath("$.data.number").value(0));
.andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
.andExpect(jsonPath("$.data[0].name").value("Template1"))
.andExpect(jsonPath("$.data[1].name").value("Template2"));
}
@Test
@@ -484,25 +414,5 @@ class NoticeConfigControllerTest {
verify(noticeConfigService, times(1)).sendTestMsg(noticeReceiver);
}
@Test
void getAllTemplates() throws Exception {
List<NoticeTemplate> templates = Arrays.asList(new NoticeTemplate(), new NoticeTemplate());
when(noticeConfigService.getAllNoticeTemplates()).thenReturn(templates);
this.mockMvc.perform(MockMvcRequestBuilders.get("/api/notice/templates/all"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
.andReturn();
}
@Test
void getAllReceivers() throws Exception {
List<NoticeReceiver> receivers = Arrays.asList(new NoticeReceiver(), new NoticeReceiver());
when(noticeConfigService.getAllNoticeReceivers()).thenReturn(receivers);
this.mockMvc.perform(MockMvcRequestBuilders.get("/api/notice/receivers/all"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
.andReturn();
}
}
@@ -27,7 +27,6 @@ import static org.mockito.Mockito.when;
import java.util.Collections;
import java.util.List;
import org.apache.hertzbeat.alert.AlerterWorkerPool;
import org.apache.hertzbeat.alert.config.AlertSseManager;
import org.apache.hertzbeat.alert.service.NoticeConfigService;
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
import org.apache.hertzbeat.common.entity.alerter.NoticeReceiver;
@@ -61,9 +60,6 @@ class AlertNoticeDispatchTest {
@Mock
private AlertNotifyHandler alertNotifyHandler;
@Mock
private AlertSseManager emitterManager;
private AlertNoticeDispatch alertNoticeDispatch;
private static final int DISPATCH_THREADS = 3;
@@ -81,8 +77,7 @@ class AlertNoticeDispatchTest {
noticeConfigService,
alertStoreHandler,
alertNotifyHandlerList,
pluginRunner,
emitterManager
pluginRunner
);
receiver = NoticeReceiver.builder()
@@ -27,6 +27,7 @@ import org.apache.hertzbeat.alert.dao.SingleAlertDao;
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
import org.apache.hertzbeat.common.entity.alerter.SingleAlert;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
@@ -38,6 +39,7 @@ import java.util.List;
/**
* Test case for {@link DbAlertStoreHandlerImpl}
*/
@Disabled
@ExtendWith(MockitoExtension.class)
class DbAlertStoreHandlerImplTest {
@@ -73,34 +75,27 @@ class DbAlertStoreHandlerImplTest {
public void testStoreNewAlert() {
String groupKey = "test-group";
groupAlert.setGroupKey(groupKey);
when(groupAlertDao.findByGroupKey(groupKey)).thenReturn(null);
SingleAlert savedSingleAlert = new SingleAlert();
when(singleAlertDao.save(any(SingleAlert.class))).thenReturn(savedSingleAlert);
GroupAlert savedGroupAlert = new GroupAlert();
when(groupAlertDao.save(any(GroupAlert.class))).thenReturn(savedGroupAlert);
dbAlertStoreHandler.store(groupAlert);
verify(singleAlertDao).save(any(SingleAlert.class));
verify(groupAlertDao).save(groupAlert);
}
@Test
public void testStoreExistingAlert() {
String groupKey = "test-group";
String fingerprint = "test-fingerprint";
groupAlert.setGroupKey(groupKey);
singleAlert.setFingerprint(fingerprint);
GroupAlert existingGroup = new GroupAlert();
existingGroup.setId(1L);
when(groupAlertDao.findByGroupKey(groupKey)).thenReturn(existingGroup);
SingleAlert existingAlert = new SingleAlert();
existingAlert.setId(1L);
existingAlert.setStatus("firing");
@@ -108,15 +103,11 @@ class DbAlertStoreHandlerImplTest {
existingAlert.setActiveAt(2000L);
existingAlert.setTriggerTimes(1);
when(singleAlertDao.findByFingerprint(fingerprint)).thenReturn(existingAlert);
when(singleAlertDao.save(any(SingleAlert.class))).thenReturn(existingAlert);
when(groupAlertDao.save(any(GroupAlert.class))).thenReturn(existingGroup);
dbAlertStoreHandler.store(groupAlert);
verify(singleAlertDao).save(any(SingleAlert.class));
verify(groupAlertDao).save(groupAlert);
assertEquals(1L, groupAlert.getId());
}
}
@@ -30,17 +30,18 @@ import java.util.Map;
import org.apache.hertzbeat.alert.dao.AlertSilenceDao;
import org.apache.hertzbeat.alert.notice.AlertNoticeDispatch;
import org.apache.hertzbeat.common.cache.CacheFactory;
import org.apache.hertzbeat.common.entity.alerter.AlertSilence;
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
/**
* Test for {@link AlarmSilenceReduce}
* Test for AlarmSilenceReduce
*/
@Disabled
class AlarmSilenceReduceTest {
@Mock
@@ -54,23 +55,21 @@ class AlarmSilenceReduceTest {
@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
CacheFactory.clearAlertSilenceCache();
when(alertSilenceDao.findAll()).thenReturn(Collections.emptyList());
alarmSilenceReduce = new AlarmSilenceReduce(alertSilenceDao, alertNoticeDispatch);
}
@Test
void whenNoSilenceRules_shouldForwardAlert() {
when(alertSilenceDao.findAlertSilencesByEnableTrue()).thenReturn(Collections.emptyList());
GroupAlert alert = createGroupAlert("firing", createLabels("service", "web"));
alarmSilenceReduce.silenceAlarm(alert);
verify(alertNoticeDispatch).dispatchAlarm(alert);
}
@Test
void whenMatchingSilenceRule_shouldNotForwardAlert() {
// Create silence rule
AlertSilence silenceRule = AlertSilence.builder()
.enable(true)
@@ -82,7 +81,7 @@ class AlarmSilenceReduceTest {
.times(0)
.build();
when(alertSilenceDao.findAlertSilencesByEnableTrue()).thenReturn(Collections.singletonList(silenceRule));
when(alertSilenceDao.findAll()).thenReturn(Collections.singletonList(silenceRule));
when(alertSilenceDao.save(any(AlertSilence.class))).thenReturn(silenceRule);
GroupAlert alert = createGroupAlert("firing", createLabels("service", "web"));
@@ -108,9 +107,9 @@ class AlarmSilenceReduceTest {
.times(0)
.build();
when(alertSilenceDao.findAlertSilencesByEnableTrue()).thenReturn(Collections.singletonList(silenceRule));
when(alertSilenceDao.findAll()).thenReturn(Collections.singletonList(silenceRule));
when(alertSilenceDao.save(any(AlertSilence.class))).thenReturn(silenceRule);
GroupAlert alert = createGroupAlert("firing", createLabels("service", "web"));
alarmSilenceReduce.silenceAlarm(alert);
@@ -131,7 +130,29 @@ class AlarmSilenceReduceTest {
.times(0)
.build();
when(alertSilenceDao.findAlertSilencesByEnableTrue()).thenReturn(Collections.singletonList(silenceRule));
when(alertSilenceDao.findAll()).thenReturn(Collections.singletonList(silenceRule));
GroupAlert alert = createGroupAlert("firing", createLabels("service", "web"));
alarmSilenceReduce.silenceAlarm(alert);
verify(alertNoticeDispatch).dispatchAlarm(alert);
verify(alertSilenceDao, never()).save(any());
}
@Test
void whenSilenceRuleDisabled_shouldForwardAlert() {
AlertSilence silenceRule = AlertSilence.builder()
.enable(false)
.matchAll(false)
.type((byte) 0)
.labels(createLabels("service", "web"))
.periodStart(LocalDateTime.now().minusHours(1).atZone(ZoneId.systemDefault()))
.periodEnd(LocalDateTime.now().plusHours(1).atZone(ZoneId.systemDefault()))
.times(0)
.build();
when(alertSilenceDao.findAll()).thenReturn(Collections.singletonList(silenceRule));
GroupAlert alert = createGroupAlert("firing", createLabels("service", "web"));
@@ -24,18 +24,16 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.apache.hertzbeat.alert.dto.AlertDefineDTO;
import org.apache.hertzbeat.alert.dto.ExportAlertDefineDTO;
import org.apache.hertzbeat.alert.service.impl.AlertDefineExcelImExportServiceImpl;
import org.apache.hertzbeat.common.util.JsonUtil;
import org.apache.hertzbeat.common.util.export.ExcelExportUtils;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
@@ -44,6 +42,7 @@ import org.mockito.junit.jupiter.MockitoExtension;
/**
* test case for {@link AlertDefineExcelImExportServiceImpl}
*/
@Disabled
@ExtendWith(MockitoExtension.class)
public class AlertDefineExcelImExportServiceTest {
@@ -62,13 +61,15 @@ public class AlertDefineExcelImExportServiceTest {
Row row = initialSheet.createRow(1);
row.createCell(0).setCellValue("app1");
row.createCell(1).setCellValue("metric1");
row.createCell(2).setCellValue("expr1");
row.createCell(3).setCellValue(10);
row.createCell(4).setCellValue(1);
row.createCell(5).setCellValue(JsonUtil.toJson(Map.of("key", "value")));
row.createCell(6).setCellValue(JsonUtil.toJson(Map.of("key", "value")));
row.createCell(7).setCellValue("template1");
row.createCell(2).setCellValue("field1");
row.createCell(3).setCellValue(true);
row.createCell(4).setCellValue("expr1");
row.createCell(5).setCellValue(1);
row.createCell(6).setCellValue(10);
row.createCell(7).setCellValue("[{\"name\":\"tag1\",\"value\":\"value1\"}]");
row.createCell(8).setCellValue(true);
row.createCell(9).setCellValue(true);
row.createCell(10).setCellValue("template1");
ByteArrayInputStream inputStream = new ByteArrayInputStream(toByteArray(initialWorkbook));
@@ -87,12 +88,9 @@ public class AlertDefineExcelImExportServiceTest {
assertEquals("app1", alertDefineDTO.getName());
assertEquals("metric1", alertDefineDTO.getType());
assertEquals("expr1", alertDefineDTO.getExpr());
assertEquals(10, alertDefineDTO.getPeriod());
assertEquals(1, alertDefineDTO.getTimes());
assertEquals(Map.of("key", "value"), alertDefineDTO.getLabels());
assertEquals(Map.of("key", "value"), alertDefineDTO.getAnnotations());
assertEquals("template1", alertDefineDTO.getTemplate());
assertEquals(10, alertDefineDTO.getTimes());
assertTrue(alertDefineDTO.getEnable());
assertEquals("template1", alertDefineDTO.getTemplate());
}
}
@@ -105,12 +103,9 @@ public class AlertDefineExcelImExportServiceTest {
alertDefineDTO.setName("app1");
alertDefineDTO.setType("metric1");
alertDefineDTO.setExpr("expr1");
alertDefineDTO.setPeriod(10);
alertDefineDTO.setTimes(1);
alertDefineDTO.setLabels(Map.of("key", "value"));
alertDefineDTO.setAnnotations(Map.of("key", "value"));
alertDefineDTO.setTemplate("template1");
alertDefineDTO.setTimes(10);
alertDefineDTO.setEnable(true);
alertDefineDTO.setTemplate("template1");
exportAlertDefineDTO.setAlertDefine(alertDefineDTO);
exportAlertDefineList.add(exportAlertDefineDTO);
@@ -120,26 +115,21 @@ public class AlertDefineExcelImExportServiceTest {
try (Workbook resultWorkbook = WorkbookFactory.create(new ByteArrayInputStream(outputStream.toByteArray()))) {
Sheet resultSheet = resultWorkbook.getSheetAt(0);
Row headerRow = resultSheet.getRow(0);
assertEquals("Name", headerRow.getCell(0).getStringCellValue());
assertEquals("Type", headerRow.getCell(1).getStringCellValue());
assertEquals("Expr", headerRow.getCell(2).getStringCellValue());
assertEquals("Period", headerRow.getCell(3).getStringCellValue());
assertEquals("Times", headerRow.getCell(4).getStringCellValue());
assertEquals("Labels", headerRow.getCell(5).getStringCellValue());
assertEquals("Annotations", headerRow.getCell(6).getStringCellValue());
assertEquals("Template", headerRow.getCell(7).getStringCellValue());
assertEquals("Enable", headerRow.getCell(8).getStringCellValue());
assertEquals("app", headerRow.getCell(0).getStringCellValue());
assertEquals("metric", headerRow.getCell(1).getStringCellValue());
Row dataRow = resultSheet.getRow(1);
assertEquals("app1", dataRow.getCell(0).getStringCellValue());
assertEquals("metric1", dataRow.getCell(1).getStringCellValue());
assertEquals("expr1", dataRow.getCell(2).getStringCellValue());
assertEquals(10, (int) dataRow.getCell(3).getNumericCellValue());
assertEquals(1, (int) dataRow.getCell(4).getNumericCellValue());
assertEquals(JsonUtil.toJson(Map.of("key", "value")), dataRow.getCell(5).getStringCellValue());
assertEquals(JsonUtil.toJson(Map.of("key", "value")), dataRow.getCell(6).getStringCellValue());
assertEquals("template1", dataRow.getCell(7).getStringCellValue());
assertEquals("field1", dataRow.getCell(2).getStringCellValue());
assertTrue(dataRow.getCell(3).getBooleanCellValue());
assertEquals("expr1", dataRow.getCell(4).getStringCellValue());
assertEquals(1, (int) dataRow.getCell(5).getNumericCellValue());
assertEquals(10, (int) dataRow.getCell(6).getNumericCellValue());
assertEquals("[{\"name\":\"tag1\",\"value\":\"value1\"}]", dataRow.getCell(7).getStringCellValue());
assertTrue(dataRow.getCell(8).getBooleanCellValue());
assertTrue(dataRow.getCell(9).getBooleanCellValue());
assertEquals("template1", dataRow.getCell(10).getStringCellValue());
}
}
}
@@ -152,4 +142,4 @@ public class AlertDefineExcelImExportServiceTest {
}
}
}
}
@@ -30,7 +30,6 @@ import static org.mockito.Mockito.when;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import org.apache.hertzbeat.alert.calculate.PeriodicAlertRuleScheduler;
import org.apache.hertzbeat.alert.dao.AlertDefineDao;
import org.apache.hertzbeat.alert.service.impl.AlertDefineServiceImpl;
import org.apache.hertzbeat.common.entity.alerter.AlertDefine;
@@ -55,9 +54,6 @@ class AlertDefineServiceTest {
@Mock
private AlertDefineDao alertDefineDao;
@Mock
private PeriodicAlertRuleScheduler periodicAlertRuleScheduler;
@Mock
private List<AlertDefineImExportService> alertDefineImExportServiceList;
@@ -68,7 +64,6 @@ class AlertDefineServiceTest {
@BeforeEach
void setUp() {
ReflectionTestUtils.setField(this.alertDefineService, "alertDefineDao", alertDefineDao);
ReflectionTestUtils.setField(this.alertDefineService, "periodicAlertRuleScheduler", periodicAlertRuleScheduler);
this.alertDefine = AlertDefine.builder()
.id(1L)
@@ -1,547 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.hertzbeat.alert.service;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import java.util.HashMap;
import org.apache.hertzbeat.alert.service.impl.DataSourceServiceImpl;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.util.List;
import java.util.Map;
import org.mockito.Mockito;
import org.apache.hertzbeat.warehouse.db.QueryExecutor;
/**
* test case for {@link DataSourceService}
*/
class DataSourceServiceTest {
private DataSourceServiceImpl dataSourceService;
@BeforeEach
void setUp() {
dataSourceService = new DataSourceServiceImpl();
}
@Test
void calculate1() {
List<Map<String, Object>> prometheusData = List.of(
new HashMap<>(Map.of("__value__", 100.0, "timestamp", 1343554, "instance", "node1")),
new HashMap<>(Map.of("__value__", 200.0, "timestamp", 1343555, "instance", "node2"))
);
QueryExecutor mockExecutor = Mockito.mock(QueryExecutor.class);
Mockito.when(mockExecutor.support("promql")).thenReturn(true);
Mockito.when(mockExecutor.execute(Mockito.anyString())).thenReturn(prometheusData);
dataSourceService.setExecutors(List.of(mockExecutor));
List<Map<String, Object>> result = dataSourceService.calculate("promql", "node_cpu_seconds_total > 150");
assertEquals(2, result.size());
assertNull(result.get(0).get("__value__"));
assertEquals(200.0, result.get(1).get("__value__"));
}
@Test
void calculate2() {
List<Map<String, Object>> prometheusData = List.of(
new HashMap<>(Map.of("__value__", 100.0, "timestamp", 1343554, "instance", "node1")),
new HashMap<>(Map.of("__value__", 200.0, "timestamp", 1343555, "instance", "node2"))
);
QueryExecutor mockExecutor = Mockito.mock(QueryExecutor.class);
Mockito.when(mockExecutor.support("promql")).thenReturn(true);
Mockito.when(mockExecutor.execute(Mockito.anyString())).thenReturn(prometheusData);
dataSourceService.setExecutors(List.of(mockExecutor));
List<Map<String, Object>> result = dataSourceService.calculate("promql", "node_cpu_seconds_total <= 100");
assertEquals(2, result.size());
assertEquals(100.0, result.get(0).get("__value__"));
assertNull(result.get(1).get("__value__"));
}
@Test
void calculate3() {
List<Map<String, Object>> prometheusData = List.of(
new HashMap<>(Map.of("__value__", 100.0, "timestamp", 1343554, "instance", "node1")),
new HashMap<>(Map.of("__value__", 200.0, "timestamp", 1343555, "instance", "node2"))
);
QueryExecutor mockExecutor = Mockito.mock(QueryExecutor.class);
Mockito.when(mockExecutor.support("promql")).thenReturn(true);
Mockito.when(mockExecutor.execute(Mockito.anyString())).thenReturn(prometheusData);
dataSourceService.setExecutors(List.of(mockExecutor));
List<Map<String, Object>> result = dataSourceService.calculate("promql", "node_cpu_seconds_total >= 200");
assertEquals(2, result.size());
assertNull(result.get(0).get("__value__"));
assertEquals(200.0, result.get(1).get("__value__"));
}
@Test
void calculate4() {
List<Map<String, Object>> prometheusData = List.of(
new HashMap<>(Map.of("__value__", 100.0, "timestamp", 1343554, "instance", "node1")),
new HashMap<>(Map.of("__value__", 200.0, "timestamp", 1343555, "instance", "node2"))
);
QueryExecutor mockExecutor = Mockito.mock(QueryExecutor.class);
Mockito.when(mockExecutor.support("promql")).thenReturn(true);
Mockito.when(mockExecutor.execute(Mockito.anyString())).thenReturn(prometheusData);
dataSourceService.setExecutors(List.of(mockExecutor));
List<Map<String, Object>> result = dataSourceService.calculate("promql", "node_cpu_seconds_total > 250");
assertEquals(2, result.size());
assertNull(result.get(0).get("__value__"));
assertNull(result.get(1).get("__value__"));
}
@Test
void calculate5() {
List<Map<String, Object>> prometheusData = List.of(
new HashMap<>(Map.of("__value__", 100.0, "timestamp", 1343554, "instance", "node1")),
new HashMap<>(Map.of("__value__", 200.0, "timestamp", 1343555, "instance", "node2"))
);
QueryExecutor mockExecutor = Mockito.mock(QueryExecutor.class);
Mockito.when(mockExecutor.support("promql")).thenReturn(true);
Mockito.when(mockExecutor.execute(Mockito.anyString())).thenReturn(prometheusData);
dataSourceService.setExecutors(List.of(mockExecutor));
List<Map<String, Object>> result = dataSourceService.calculate("promql", "node_cpu_seconds_total < 100");
assertEquals(2, result.size());
assertNull(result.get(0).get("__value__"));
assertNull(result.get(1).get("__value__"));
}
@Test
void calculate6() {
List<Map<String, Object>> prometheusData = List.of(
new HashMap<>(Map.of("__value__", 100.0, "timestamp", 1343554, "instance", "node1")),
new HashMap<>(Map.of("__value__", 200.0, "timestamp", 1343555, "instance", "node2"))
);
QueryExecutor mockExecutor = Mockito.mock(QueryExecutor.class);
Mockito.when(mockExecutor.support("promql")).thenReturn(true);
Mockito.when(mockExecutor.execute(Mockito.anyString())).thenReturn(prometheusData);
dataSourceService.setExecutors(List.of(mockExecutor));
List<Map<String, Object>> result = dataSourceService.calculate("promql", "node_cpu_seconds_total > 200");
assertEquals(2, result.size());
assertNull(result.get(0).get("__value__"));
assertNull(result.get(1).get("__value__"));
}
@Test
void calculate7() {
List<Map<String, Object>> prometheusData = List.of(
new HashMap<>(Map.of("__value__", 100.0, "timestamp", 1343554, "instance", "node1")),
new HashMap<>(Map.of("__value__", 200.0, "timestamp", 1343555, "instance", "node2"))
);
QueryExecutor mockExecutor = Mockito.mock(QueryExecutor.class);
Mockito.when(mockExecutor.support("promql")).thenReturn(true);
Mockito.when(mockExecutor.execute(Mockito.anyString())).thenReturn(prometheusData);
dataSourceService.setExecutors(List.of(mockExecutor));
List<Map<String, Object>> result = dataSourceService.calculate("promql", "(node_cpu_seconds_total <= 100)");
assertEquals(2, result.size());
assertEquals(100.0, result.get(0).get("__value__"));
assertNull(result.get(1).get("__value__"));
}
@Test
void calculate8() {
List<Map<String, Object>> prometheusData = List.of(
new HashMap<>(Map.of("__value__", 100.0, "timestamp", 1343554, "instance", "node1")),
new HashMap<>(Map.of("__value__", 200.0, "timestamp", 1343555, "instance", "node2"))
);
QueryExecutor mockExecutor = Mockito.mock(QueryExecutor.class);
Mockito.when(mockExecutor.support("promql")).thenReturn(true);
Mockito.when(mockExecutor.execute(Mockito.anyString())).thenReturn(prometheusData);
dataSourceService.setExecutors(List.of(mockExecutor));
List<Map<String, Object>> result = dataSourceService.calculate("promql", "node_cpu_seconds_total{mode=\"user\"}[4m] <= 100");
assertEquals(2, result.size());
assertEquals(100.0, result.get(0).get("__value__"));
assertNull(result.get(1).get("__value__"));
}
@Test
void calculate9() {
List<Map<String, Object>> prometheusData = List.of(
new HashMap<>(Map.of("__value__", 100.0, "timestamp", 1343554, "instance", "node1")),
new HashMap<>(Map.of("__value__", 200.0, "timestamp", 1343555, "instance", "node2"))
);
QueryExecutor mockExecutor = Mockito.mock(QueryExecutor.class);
Mockito.when(mockExecutor.support("promql")).thenReturn(true);
Mockito.when(mockExecutor.execute(Mockito.anyString())).thenReturn(prometheusData);
dataSourceService.setExecutors(List.of(mockExecutor));
List<Map<String, Object>> result = dataSourceService.calculate("promql", "node_cpu_seconds_total{mode=\"user\"} == 100");
assertEquals(2, result.size());
assertEquals(100.0, result.get(0).get("__value__"));
assertNull(result.get(1).get("__value__"));
}
@Test
void calculate10() {
List<Map<String, Object>> prometheusData = List.of(
new HashMap<>(Map.of("__value__", 100.0, "timestamp", 1343554, "instance", "node1")),
new HashMap<>(Map.of("__value__", 200.0, "timestamp", 1343555, "instance", "node2"))
);
QueryExecutor mockExecutor = Mockito.mock(QueryExecutor.class);
Mockito.when(mockExecutor.support("promql")).thenReturn(true);
Mockito.when(mockExecutor.execute(Mockito.anyString())).thenReturn(prometheusData);
dataSourceService.setExecutors(List.of(mockExecutor));
List<Map<String, Object>> result = dataSourceService.calculate("promql", "node_cpu_seconds_total{mode=\"user\"} != 100");
assertEquals(2, result.size());
assertNull(result.get(0).get("__value__"));
assertEquals(200.0, result.get(1).get("__value__"));
}
@Test
void calculate11() {
List<Map<String, Object>> prometheusData = List.of(
new HashMap<>(Map.of("__value__", 100.0, "timestamp", 1343554, "instance", "node1")),
new HashMap<>(Map.of("__value__", 200.0, "timestamp", 1343555, "instance", "node2"))
);
QueryExecutor mockExecutor = Mockito.mock(QueryExecutor.class);
Mockito.when(mockExecutor.support("promql")).thenReturn(true);
Mockito.when(mockExecutor.execute(Mockito.anyString())).thenReturn(prometheusData);
dataSourceService.setExecutors(List.of(mockExecutor));
List<Map<String, Object>> result = dataSourceService.calculate("promql", "node_cpu_seconds_total{mode=\"user\"} > 50 and node_cpu_seconds_total{mode=\"idle\"} < 120");
assertEquals(1, result.size());
assertEquals(100.0, result.get(0).get("__value__"));
}
@Test
void calculate12() {
List<Map<String, Object>> prometheusData = List.of(
new HashMap<>(Map.of("__value__", 100.0, "timestamp", 1343554, "instance", "node1")),
new HashMap<>(Map.of("__value__", 200.0, "timestamp", 1343555, "instance", "node2"))
);
QueryExecutor mockExecutor = Mockito.mock(QueryExecutor.class);
Mockito.when(mockExecutor.support("promql")).thenReturn(true);
Mockito.when(mockExecutor.execute(Mockito.anyString())).thenReturn(prometheusData);
dataSourceService.setExecutors(List.of(mockExecutor));
List<Map<String, Object>> result = dataSourceService.calculate("promql", "(node_cpu_seconds_total{mode=\"user\"} > 50) and (node_cpu_seconds_total{mode=\"idle\"} < 120)");
assertEquals(1, result.size());
assertEquals(100.0, result.get(0).get("__value__"));
}
@Test
void calculate13() {
List<Map<String, Object>> prometheusData = List.of(
new HashMap<>(Map.of("__value__", 100.0, "timestamp", 1343554, "instance", "node1")),
new HashMap<>(Map.of("__value__", 200.0, "timestamp", 1343555, "instance", "node2"))
);
QueryExecutor mockExecutor = Mockito.mock(QueryExecutor.class);
Mockito.when(mockExecutor.support("promql")).thenReturn(true);
Mockito.when(mockExecutor.execute(Mockito.anyString())).thenReturn(prometheusData);
dataSourceService.setExecutors(List.of(mockExecutor));
List<Map<String, Object>> result = dataSourceService.calculate("promql", "node_cpu_seconds_total{mode=\"user\"} > 150 and node_cpu_seconds_total{mode=\"idle\"} < 220");
assertEquals(1, result.size());
assertEquals(200.0, result.get(0).get("__value__"));
}
@Test
void calculate14() {
List<Map<String, Object>> prometheusData = List.of(
new HashMap<>(Map.of("__value__", 100.0, "timestamp", 1343554, "instance", "node1")),
new HashMap<>(Map.of("__value__", 200.0, "timestamp", 1343555, "instance", "node2"))
);
QueryExecutor mockExecutor = Mockito.mock(QueryExecutor.class);
Mockito.when(mockExecutor.support("promql")).thenReturn(true);
Mockito.when(mockExecutor.execute(Mockito.anyString())).thenReturn(prometheusData);
dataSourceService.setExecutors(List.of(mockExecutor));
List<Map<String, Object>> result = dataSourceService.calculate("promql", "(node_cpu_seconds_total{mode=\"user\"} > 150) and (node_cpu_seconds_total{mode=\"idle\"} < 220)");
assertEquals(1, result.size());
assertEquals(200.0, result.get(0).get("__value__"));
}
@Test
void calculate15() {
List<Map<String, Object>> prometheusData1 = List.of(
new HashMap<>(Map.of("__value__", 100.0, "timestamp", 1343554, "instance", "node1")),
new HashMap<>(Map.of("__value__", 200.0, "timestamp", 1343555, "instance", "node2"))
);
List<Map<String, Object>> prometheusData2 = List.of(
new HashMap<>(Map.of("__value__", 100.0, "timestamp", 1343554, "instance", "node1")),
new HashMap<>(Map.of("__value__", 200.0, "timestamp", 1343555, "instance", "node2"))
);
QueryExecutor mockExecutor = Mockito.mock(QueryExecutor.class);
Mockito.when(mockExecutor.support("promql")).thenReturn(true);
Mockito.when(mockExecutor.execute("node_cpu_seconds_total{mode=\"user\"}")).thenReturn(prometheusData1);
Mockito.when(mockExecutor.execute("node_cpu_seconds_total{mode=\"idle\"}")).thenReturn(prometheusData2);
dataSourceService.setExecutors(List.of(mockExecutor));
List<Map<String, Object>> result = dataSourceService.calculate("promql", "node_cpu_seconds_total{mode=\"user\"} > 250 and node_cpu_seconds_total{mode=\"idle\"} < 220");
assertEquals(1, result.size());
assertNull(result.get(0).get("__value__"));
}
@Test
void calculate16() {
List<Map<String, Object>> prometheusData1 = List.of(
new HashMap<>(Map.of("__value__", 100.0, "timestamp", 1343554, "instance", "node1")),
new HashMap<>(Map.of("__value__", 200.0, "timestamp", 1343555, "instance", "node2"))
);
List<Map<String, Object>> prometheusData2 = List.of(
new HashMap<>(Map.of("__value__", 100.0, "timestamp", 1343554, "instance", "node1")),
new HashMap<>(Map.of("__value__", 200.0, "timestamp", 1343555, "instance", "node2"))
);
QueryExecutor mockExecutor = Mockito.mock(QueryExecutor.class);
Mockito.when(mockExecutor.support("promql")).thenReturn(true);
Mockito.when(mockExecutor.execute("node_cpu_seconds_total{mode=\"user\"}")).thenReturn(prometheusData1);
Mockito.when(mockExecutor.execute("node_cpu_seconds_total{mode=\"idle\"}")).thenReturn(prometheusData2);
dataSourceService.setExecutors(List.of(mockExecutor));
List<Map<String, Object>> result = dataSourceService.calculate("promql", "node_cpu_seconds_total{mode=\"user\"} > 50 and node_cpu_seconds_total{mode=\"idle\"} < 20");
assertEquals(1, result.size());
assertNull(result.get(0).get("__value__"));
}
@Test
void calculate17() {
List<Map<String, Object>> prometheusData1 = List.of(
new HashMap<>(Map.of("__value__", 100.0, "timestamp", 1343554, "instance", "node1")),
new HashMap<>(Map.of("__value__", 200.0, "timestamp", 1343555, "instance", "node2"))
);
List<Map<String, Object>> prometheusData2 = List.of(
new HashMap<>(Map.of("__value__", 100.0, "timestamp", 1343554, "instance", "node1")),
new HashMap<>(Map.of("__value__", 200.0, "timestamp", 1343555, "instance", "node2"))
);
QueryExecutor mockExecutor = Mockito.mock(QueryExecutor.class);
Mockito.when(mockExecutor.support("promql")).thenReturn(true);
Mockito.when(mockExecutor.execute("node_cpu_seconds_total{mode=\"user\"}")).thenReturn(prometheusData1);
Mockito.when(mockExecutor.execute("node_cpu_seconds_total{mode=\"idle\"}")).thenReturn(prometheusData2);
dataSourceService.setExecutors(List.of(mockExecutor));
List<Map<String, Object>> result = dataSourceService.calculate("promql", "node_cpu_seconds_total{mode=\"user\"} > 150 or node_cpu_seconds_total{mode=\"idle\"} < 20");
assertEquals(1, result.size());
assertEquals(200.0, result.get(0).get("__value__"));
}
@Test
void calculate18() {
List<Map<String, Object>> prometheusData1 = List.of(
new HashMap<>(Map.of("__value__", 100.0, "timestamp", 1343554, "instance", "node1")),
new HashMap<>(Map.of("__value__", 200.0, "timestamp", 1343555, "instance", "node2"))
);
List<Map<String, Object>> prometheusData2 = List.of(
new HashMap<>(Map.of("__value__", 100.0, "timestamp", 1343554, "instance", "node1")),
new HashMap<>(Map.of("__value__", 200.0, "timestamp", 1343555, "instance", "node2"))
);
QueryExecutor mockExecutor = Mockito.mock(QueryExecutor.class);
Mockito.when(mockExecutor.support("promql")).thenReturn(true);
Mockito.when(mockExecutor.execute("node_cpu_seconds_total{mode=\"user\"}")).thenReturn(prometheusData1);
Mockito.when(mockExecutor.execute("node_cpu_seconds_total{mode=\"idle\"}")).thenReturn(prometheusData2);
dataSourceService.setExecutors(List.of(mockExecutor));
List<Map<String, Object>> result = dataSourceService.calculate("promql", "node_cpu_seconds_total{mode=\"user\"} > 250 or node_cpu_seconds_total{mode=\"idle\"} < 120");
assertEquals(1, result.size());
assertEquals(100.0, result.get(0).get("__value__"));
}
@Test
void calculate19() {
List<Map<String, Object>> prometheusData1 = List.of(
new HashMap<>(Map.of("__value__", 100.0, "timestamp", 1343554, "instance", "node1")),
new HashMap<>(Map.of("__value__", 200.0, "timestamp", 1343555, "instance", "node2"))
);
List<Map<String, Object>> prometheusData2 = List.of(
new HashMap<>(Map.of("__value__", 100.0, "timestamp", 1343554, "instance", "node1")),
new HashMap<>(Map.of("__value__", 200.0, "timestamp", 1343555, "instance", "node2"))
);
QueryExecutor mockExecutor = Mockito.mock(QueryExecutor.class);
Mockito.when(mockExecutor.support("promql")).thenReturn(true);
Mockito.when(mockExecutor.execute("node_cpu_seconds_total{mode=\"user\"}")).thenReturn(prometheusData1);
Mockito.when(mockExecutor.execute("node_cpu_seconds_total{mode=\"idle\"}")).thenReturn(prometheusData2);
dataSourceService.setExecutors(List.of(mockExecutor));
List<Map<String, Object>> result = dataSourceService.calculate("promql", "node_cpu_seconds_total{mode=\"user\"} > 250 or node_cpu_seconds_total{mode=\"idle\"} < 20");
assertEquals(1, result.size());
assertNull(result.get(0).get("__value__"));
}
@Test
void calculate20() {
List<Map<String, Object>> prometheusData1 = List.of(
new HashMap<>(Map.of("__value__", 100.0, "timestamp", 1343554, "instance", "node1")),
new HashMap<>(Map.of("__value__", 200.0, "timestamp", 1343555, "instance", "node2"))
);
List<Map<String, Object>> prometheusData2 = List.of(
new HashMap<>(Map.of("__value__", 100.0, "timestamp", 1343554, "key", "node1")),
new HashMap<>(Map.of("__value__", 200.0, "timestamp", 1343555, "book", "node2"))
);
QueryExecutor mockExecutor = Mockito.mock(QueryExecutor.class);
Mockito.when(mockExecutor.support("promql")).thenReturn(true);
Mockito.when(mockExecutor.execute("node_cpu_seconds_total{mode=\"user\"}")).thenReturn(prometheusData1);
Mockito.when(mockExecutor.execute("node_cpu_seconds_total{mode=\"idle\"}")).thenReturn(prometheusData2);
dataSourceService.setExecutors(List.of(mockExecutor));
List<Map<String, Object>> result = dataSourceService.calculate("promql", "node_cpu_seconds_total{mode=\"user\"} > 50 or node_cpu_seconds_total{mode=\"idle\"} < 320");
assertEquals(1, result.size());
assertNotNull(result.get(0).get("__value__"));
}
@Test
void calculate21() {
List<Map<String, Object>> prometheusData1 = List.of(
new HashMap<>(Map.of("__value__", 100.0, "timestamp", 1343554, "instance", "node1")),
new HashMap<>(Map.of("__value__", 200.0, "timestamp", 1343555, "instance", "node2"))
);
List<Map<String, Object>> prometheusData2 = List.of(
new HashMap<>(Map.of("__value__", 100.0, "timestamp", 1343554, "key", "node1")),
new HashMap<>(Map.of("__value__", 200.0, "timestamp", 1343555, "book", "node2"))
);
QueryExecutor mockExecutor = Mockito.mock(QueryExecutor.class);
Mockito.when(mockExecutor.support("promql")).thenReturn(true);
Mockito.when(mockExecutor.execute("node_cpu_seconds_total{mode=\"user\"}")).thenReturn(prometheusData1);
Mockito.when(mockExecutor.execute("node_cpu_seconds_total{mode=\"idle\"}")).thenReturn(prometheusData2);
dataSourceService.setExecutors(List.of(mockExecutor));
List<Map<String, Object>> result = dataSourceService.calculate("promql", "node_cpu_seconds_total{mode=\"user\"} > 50 unless node_cpu_seconds_total{mode=\"idle\"} < 320");
assertEquals(1, result.size());
assertNull(result.get(0).get("__value__"));
}
@Test
void calculate22() {
List<Map<String, Object>> prometheusData1 = List.of(
new HashMap<>(Map.of("__value__", 100.0, "timestamp", 1343554, "instance", "node1")),
new HashMap<>(Map.of("__value__", 200.0, "timestamp", 1343555, "instance", "node2"))
);
List<Map<String, Object>> prometheusData2 = List.of(
new HashMap<>(Map.of("__value__", 100.0, "timestamp", 1343554, "key", "node1")),
new HashMap<>(Map.of("__value__", 200.0, "timestamp", 1343555, "book", "node2"))
);
QueryExecutor mockExecutor = Mockito.mock(QueryExecutor.class);
Mockito.when(mockExecutor.support("promql")).thenReturn(true);
Mockito.when(mockExecutor.execute("node_cpu_seconds_total{mode=\"user\"}")).thenReturn(prometheusData1);
Mockito.when(mockExecutor.execute("node_cpu_seconds_total{mode=\"idle\"}")).thenReturn(prometheusData2);
dataSourceService.setExecutors(List.of(mockExecutor));
List<Map<String, Object>> result = dataSourceService.calculate("promql", "node_cpu_seconds_total{mode=\"user\"} > 50 unless node_cpu_seconds_total{mode=\"idle\"} < 20");
assertEquals(1, result.size());
assertNotNull(result.get(0).get("__value__"));
}
@Test
void calculate23() {
List<Map<String, Object>> prometheusData1 = List.of(
new HashMap<>(Map.of("__value__", 100.0, "timestamp", 1343554, "instance", "node1")),
new HashMap<>(Map.of("__value__", 200.0, "timestamp", 1343555, "instance", "node2"))
);
List<Map<String, Object>> prometheusData2 = List.of(
new HashMap<>(Map.of("__value__", 100.0, "timestamp", 1343554, "key", "node1")),
new HashMap<>(Map.of("__value__", 200.0, "timestamp", 1343555, "book", "node2"))
);
QueryExecutor mockExecutor = Mockito.mock(QueryExecutor.class);
Mockito.when(mockExecutor.support("promql")).thenReturn(true);
Mockito.when(mockExecutor.execute("node_cpu_seconds_total{mode=\"user\"}")).thenReturn(prometheusData1);
Mockito.when(mockExecutor.execute("node_cpu_seconds_total{mode=\"idle\"}")).thenReturn(prometheusData2);
dataSourceService.setExecutors(List.of(mockExecutor));
List<Map<String, Object>> result = dataSourceService.calculate("promql", "node_cpu_seconds_total{mode=\"user\"} > 250 unless node_cpu_seconds_total{mode=\"idle\"} < 20");
assertEquals(1, result.size());
assertNull(result.get(0).get("__value__"));
}
@Test
void calculate24() {
List<Map<String, Object>> prometheusData = List.of();
QueryExecutor mockExecutor = Mockito.mock(QueryExecutor.class);
Mockito.when(mockExecutor.support("promql")).thenReturn(true);
Mockito.when(mockExecutor.execute(Mockito.anyString())).thenReturn(prometheusData);
dataSourceService.setExecutors(List.of(mockExecutor));
List<Map<String, Object>> result = dataSourceService.calculate("promql", "node_cpu_seconds_total > 150");
assertEquals(0, result.size());
}
@Test
void calculate25() {
List<Map<String, Object>> prometheusData = List.of();
QueryExecutor mockExecutor = Mockito.mock(QueryExecutor.class);
Mockito.when(mockExecutor.support("promql")).thenReturn(true);
Mockito.when(mockExecutor.execute(Mockito.anyString())).thenReturn(prometheusData);
dataSourceService.setExecutors(List.of(mockExecutor));
List<Map<String, Object>> result = dataSourceService.calculate("promql", "node_cpu_seconds_total{mode=\"user\"} > 250 unless node_cpu_seconds_total{mode=\"idle\"} < 20");
assertEquals(0, result.size());
}
@Test
void calculate26() {
List<Map<String, Object>> prometheusData = List.of(
new HashMap<>(Map.of("__value__", 200.0, "timestamp", 1343555, "instance", "node2"))
);
QueryExecutor mockExecutor = Mockito.mock(QueryExecutor.class);
Mockito.when(mockExecutor.support("promql")).thenReturn(true);
Mockito.when(mockExecutor.execute(Mockito.anyString())).thenReturn(prometheusData);
dataSourceService.setExecutors(List.of(mockExecutor));
List<Map<String, Object>> result = dataSourceService.calculate("promql", "node_cpu_seconds_total > 150");
assertEquals(1, result.size());
assertEquals(200.0, result.get(0).get("__value__"));
}
}
@@ -17,42 +17,33 @@
package org.apache.hertzbeat.alert.service;
import org.apache.hertzbeat.alert.dao.NoticeReceiverDao;
import org.apache.hertzbeat.alert.dao.NoticeRuleDao;
import org.apache.hertzbeat.alert.dao.NoticeTemplateDao;
import org.apache.hertzbeat.alert.notice.AlertNoticeDispatch;
import org.apache.hertzbeat.alert.service.impl.NoticeConfigServiceImpl;
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
import org.apache.hertzbeat.common.entity.alerter.NoticeReceiver;
import org.apache.hertzbeat.common.entity.alerter.NoticeRule;
import org.apache.hertzbeat.common.entity.alerter.NoticeTemplate;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import java.util.Arrays;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.apache.hertzbeat.alert.service.impl.NoticeConfigServiceImpl;
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
import org.apache.hertzbeat.common.entity.alerter.NoticeReceiver;
import org.apache.hertzbeat.common.entity.alerter.NoticeRule;
import org.apache.hertzbeat.common.entity.alerter.NoticeTemplate;
import org.apache.hertzbeat.alert.notice.AlertNoticeDispatch;
import org.apache.hertzbeat.alert.dao.NoticeReceiverDao;
import org.apache.hertzbeat.alert.dao.NoticeRuleDao;
import org.apache.hertzbeat.alert.dao.NoticeTemplateDao;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.data.jpa.domain.Specification;
/**
* Test case for {@link NoticeConfigService}
*/
@Disabled
@ExtendWith(MockitoExtension.class)
class NoticeConfigServiceTest {
@@ -67,130 +58,26 @@ class NoticeConfigServiceTest {
@InjectMocks
private NoticeConfigServiceImpl noticeConfigService;
private NoticeReceiver receiver1;
private NoticeReceiver receiver2;
private NoticeTemplate template1;
private NoticeTemplate template2;
private NoticeRule rule1;
private NoticeRule rule2;
@BeforeEach
void setUp() {
receiver1 = new NoticeReceiver();
receiver1.setId(1L);
receiver1.setName("Receiver1");
receiver2 = new NoticeReceiver();
receiver2.setId(2L);
receiver2.setName("Receiver2");
template1 = new NoticeTemplate();
template1.setId(1L);
template1.setName("Template1");
template2 = new NoticeTemplate();
template2.setId(2L);
template2.setName("Template2");
rule1 = new NoticeRule();
rule1.setId(1L);
rule1.setName("Rule1");
rule2 = new NoticeRule();
rule2.setId(2L);
rule2.setName("Rule2");
}
@Test
void getNoticeReceivers() {
Page<NoticeReceiver> receiverPage = new PageImpl<>(
Arrays.asList(receiver1, receiver2),
PageRequest.of(0, 8, Sort.by(Sort.Direction.DESC, "id")),
2
);
when(noticeReceiverDao.findAll(any(Specification.class), any(PageRequest.class))).thenReturn(receiverPage);
Page<NoticeReceiver> result = noticeConfigService.getNoticeReceivers("Receiver", 0, 8);
assertEquals(2, result.getTotalElements());
assertEquals(1, result.getTotalPages());
assertEquals(8, result.getSize());
assertEquals(0, result.getNumber());
assertEquals(receiver1, result.getContent().get(0));
assertEquals(receiver2, result.getContent().get(1));
verify(noticeReceiverDao, times(1)).findAll(any(Specification.class), any(PageRequest.class));
}
@Test
void getAllNoticeReceivers() {
when(noticeReceiverDao.findAll()).thenReturn(Arrays.asList(receiver1, receiver2));
List<NoticeReceiver> result = noticeConfigService.getAllNoticeReceivers();
assertEquals(2, result.size());
assertEquals(receiver1, result.get(0));
assertEquals(receiver2, result.get(1));
verify(noticeReceiverDao, times(1)).findAll();
noticeConfigService.getNoticeReceivers(null);
verify(noticeReceiverDao, times(1)).findAll(any(Specification.class));
}
@Test
void getNoticeTemplates() {
Page<NoticeTemplate> templatePage = new PageImpl<>(
Arrays.asList(template1, template2),
PageRequest.of(0, 8, Sort.by(Sort.Direction.DESC, "id")),
2
);
when(noticeTemplateDao.findAll(any(Specification.class), any(PageRequest.class))).thenReturn(templatePage);
Page<NoticeTemplate> result = noticeConfigService.getNoticeTemplates("Template", false, 0, 8);
assertEquals(2, result.getTotalElements());
assertEquals(1, result.getTotalPages());
assertEquals(8, result.getSize());
assertEquals(0, result.getNumber());
assertEquals(template1, result.getContent().get(0));
assertEquals(template2, result.getContent().get(1));
verify(noticeTemplateDao, times(1)).findAll(any(Specification.class), any(PageRequest.class));
}
@Test
void getAllNoticeTemplates() {
when(noticeTemplateDao.findAll()).thenReturn(Arrays.asList(template1, template2));
List<NoticeTemplate> result = noticeConfigService.getAllNoticeTemplates();
assert result.size() >= 2;
assertEquals(template1, result.get(result.size() - 2));
assertEquals(template2, result.get(result.size() - 1));
verify(noticeTemplateDao, times(1)).findAll();
noticeConfigService.getNoticeTemplates(null);
verify(noticeTemplateDao, times(1)).findAll(any(Specification.class));
}
@Test
void getNoticeRules() {
Page<NoticeRule> rulePage = new PageImpl<>(
Arrays.asList(rule1, rule2),
PageRequest.of(0, 8, Sort.by(Sort.Direction.DESC, "id")),
2
);
when(noticeRuleDao.findAll(any(Specification.class), any(PageRequest.class))).thenReturn(rulePage);
Page<NoticeRule> result = noticeConfigService.getNoticeRules("Rule", 0, 8);
assertEquals(2, result.getTotalElements());
assertEquals(1, result.getTotalPages());
assertEquals(8, result.getSize());
assertEquals(0, result.getNumber());
assertEquals(rule1, result.getContent().get(0));
assertEquals(rule2, result.getContent().get(1));
verify(noticeRuleDao, times(1)).findAll(any(Specification.class), any(PageRequest.class));
noticeConfigService.getNoticeRules(null);
verify(noticeRuleDao, times(1)).findAll(any(Specification.class));
}
@Test
@@ -155,9 +155,5 @@
<artifactId>plc4j-driver-modbus</artifactId>
<version>0.12.0</version>
</dependency>
<dependency>
<groupId>org.apache.sshd</groupId>
<artifactId>sshd-sftp</artifactId>
</dependency>
</dependencies>
</project>
@@ -17,11 +17,9 @@
package org.apache.hertzbeat.collector.collect.ftp;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.hertzbeat.collector.collect.AbstractCollect;
@@ -31,10 +29,6 @@ import org.apache.hertzbeat.common.entity.job.Metrics;
import org.apache.hertzbeat.common.entity.job.protocol.FtpProtocol;
import org.apache.hertzbeat.common.entity.message.CollectRep;
import org.apache.hertzbeat.common.util.CommonUtil;
import org.apache.sshd.client.SshClient;
import org.apache.sshd.client.session.ClientSession;
import org.apache.sshd.sftp.client.SftpClient;
import org.apache.sshd.sftp.client.SftpClientFactory;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@@ -62,14 +56,33 @@ public class FtpCollectImpl extends AbstractCollect {
Assert.hasText(ftpProtocol.getTimeout(), "Ftp Protocol timeout is required.");
}
@Override
public void collect(CollectRep.MetricsData.Builder builder, Metrics metrics) {
boolean ssl = Boolean.parseBoolean(metrics.getFtp().getSsl());
if (ssl){
handleSftpCollect(builder, metrics);
} else {
handleFtpCollect(builder, metrics);
FTPClient ftpClient = new FTPClient();
FtpProtocol ftpProtocol = metrics.getFtp();
// Set timeout
ftpClient.setControlKeepAliveReplyTimeout(Integer.parseInt(ftpProtocol.getTimeout()));
// Collect data to load in CollectRep.ValueRow.Builder's object
CollectRep.ValueRow.Builder valueRowBuilder = CollectRep.ValueRow.newBuilder();
Map<String, String> valueMap;
try {
valueMap = collectValue(ftpClient, ftpProtocol);
metrics.getAliasFields().forEach(it -> {
if (valueMap.containsKey(it)) {
String fieldValue = valueMap.get(it);
valueRowBuilder.addColumn(Objects.requireNonNullElse(fieldValue, CommonConstants.NULL_VALUE));
} else {
valueRowBuilder.addColumn(CommonConstants.NULL_VALUE);
}
});
} catch (Exception e) {
builder.setCode(CollectRep.Code.UN_CONNECTABLE);
builder.setMsg(e.getMessage());
return;
}
builder.addValueRow(valueRowBuilder.build());
}
/**
@@ -100,23 +113,8 @@ public class FtpCollectImpl extends AbstractCollect {
};
}
private Map<String, String> collectValue(SftpClient sftpClient, FtpProtocol ftpProtocol) {
boolean isActive;
String responseTime;
try {
long startTime = System.currentTimeMillis();
sftpClient.stat(ftpProtocol.getDirection());
isActive = true;
long endTime = System.currentTimeMillis();
responseTime = String.valueOf(endTime - startTime);
} catch (IOException e) {
throw new IllegalArgumentException("[SFTPClient] error: {}" + CommonUtil.getMessageFromThrowable(e), e);
}
return Map.of("isActive", Boolean.toString(isActive), "responseTime", responseTime);
}
/**
* ftp login
* login
*/
private void login(FTPClient ftpClient, FtpProtocol ftpProtocol) {
try {
@@ -149,84 +147,8 @@ public class FtpCollectImpl extends AbstractCollect {
}
}
private ClientSession connect(SshClient client, FtpProtocol ftpProtocol) {
client.start();
try {
ClientSession session = client.connect(ftpProtocol.getUsername(), ftpProtocol.getHost(), Integer.parseInt(ftpProtocol.getPort()))
.verify(Integer.parseInt(ftpProtocol.getTimeout()))
.getSession();
session.addPasswordIdentity(ftpProtocol.getPassword());
session.auth().verify(Integer.parseInt(ftpProtocol.getTimeout()));
return session;
} catch (Exception e) {
throw new IllegalArgumentException("[sftp connection] error: {}" + CommonUtil.getMessageFromThrowable(e), e);
}
}
@Override
public String supportProtocol() {
return DispatchConstants.PROTOCOL_FTP;
}
private void handleFtpCollect(CollectRep.MetricsData.Builder builder, Metrics metrics) {
FTPClient ftpClient = new FTPClient();
FtpProtocol ftpProtocol = metrics.getFtp();
// Set timeout
ftpClient.setControlKeepAliveReplyTimeout(Integer.parseInt(ftpProtocol.getTimeout()));
// Collect data to load in CollectRep.ValueRow.Builder's object
CollectRep.ValueRow.Builder valueRowBuilder = CollectRep.ValueRow.newBuilder();
Map<String, String> valueMap;
try {
valueMap = collectValue(ftpClient, ftpProtocol);
metrics.getAliasFields().forEach(it -> {
if (valueMap.containsKey(it)) {
String fieldValue = valueMap.get(it);
valueRowBuilder.addColumn(Objects.requireNonNullElse(fieldValue, CommonConstants.NULL_VALUE));
} else {
valueRowBuilder.addColumn(CommonConstants.NULL_VALUE);
}
});
} catch (Exception e) {
builder.setCode(CollectRep.Code.UN_CONNECTABLE);
builder.setMsg(e.getMessage());
return;
}
builder.addValueRow(valueRowBuilder.build());
}
private void handleSftpCollect(CollectRep.MetricsData.Builder builder, Metrics metrics) {
FtpProtocol ftpProtocol = metrics.getFtp();
ClientSession session = null;
SftpClient sftpClient = null;
SshClient client = null;
try {
client = SshClient.setUpDefaultClient();
session = connect(client, ftpProtocol);
sftpClient = SftpClientFactory.instance().createSftpClient(session);
Map<String, String> valueMap = collectValue(sftpClient, ftpProtocol);
CollectRep.ValueRow.Builder valueRowBuilder = CollectRep.ValueRow.newBuilder();
metrics.getAliasFields().forEach(it ->
valueRowBuilder.addColumn(valueMap.getOrDefault(it, CommonConstants.NULL_VALUE))
);
builder.addValueRow(valueRowBuilder.build());
} catch (Exception e) {
builder.setCode(CollectRep.Code.UN_CONNECTABLE);
builder.setMsg(e.getMessage());
} finally {
try {
if (sftpClient != null && sftpClient.isOpen()){
sftpClient.close();
}
if (session != null && session.isOpen()){
session.close();
}
if (client != null && client.isOpen()){
client.close();
}
} catch (Exception e){
log.error("[SFTPClient] error while closing: {}", CommonUtil.getMessageFromThrowable(e), e);
}
}
}
}
}
@@ -17,63 +17,68 @@
package org.apache.hertzbeat.collector.collect.prometheus.parser;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertNotNull;
class OnlineParserTest {
@Disabled // Disabled due to the fact that the URL is not reachable unless you have the Prometheus server running
@Test
void parseMetrics() throws Exception {
URL url = new URL("http://localhost:9090/metrics");
InputStream inputStream = url.openStream();
Map<String, MetricFamily> metricFamilyMap = OnlineParser.parseMetrics(inputStream);
assertNotNull(metricFamilyMap);
void parseMetrics() {
try {
URL url = new URL("http://localhost:9090/metrics");
InputStream inputStream = url.openStream();
Map<String, MetricFamily> metricFamilyMap = OnlineParser.parseMetrics(inputStream);
System.out.println(1);
}
catch (Exception e) {
e.printStackTrace();
}
}
@Test
void parseMetrics2() throws Exception {
String str = """
# HELP go_gc_duration_seconds A summary of the pause duration of garbage collection cycles.
# TYPE go_gc_duration_seconds summary
go_gc_duration_seconds { quantile="0"} 2.0209e-05 321312
go_gc_duration_seconds{ quantile = "0.25" } 6.6917e-05
go_gc_duration_seconds{quantile="0.5"} -Inf
go_gc_duration_seconds{ quantile = "0.75"} +Inf
go_gc_duration_seconds{quantile="1"} NaN
go_gc_duration_seconds_sum 0.001134793 321314
go_gc_duration_seconds_count 5 43
# HELP go_goroutines Number of goroutines that currently exist.
# TYPE go_goroutines gauge
go_goroutines 32
# HELP go_info Information about the Go environment.
# TYPE go_info gauge
go_info{version="go1.21.6"} 1
# HELP go_memstats_alloc_bytes Number of bytes allocated and still in use.
# TYPE go_memstats_alloc_bytes gauge
go_memstats_alloc_bytes 1.5716224e+07
# HELP go_memstats_alloc_bytes_total Total number of bytes allocated, even if freed.
# TYPE go_memstats_alloc_bytes_total counter
go_memstats_alloc_bytes_total 2.0707544e+07
# HELP go_memstats_buck_hash_sys_bytes Number of bytes used by the profiling bucket hash table.
# TYPE go_memstats_buck_hash_sys_bytes gauge
go_memstats_buck_hash_sys_bytes 1.457881e+06
# HELP go_memstats_frees_total Total number of frees.
# TYPE go_memstats_frees_total counter
go_memstats_frees_total 50438
# HELP go_memstats_gc_sys_bytes Number of bytes used for garbage collection system metadata.
# TYPE go_memstats_gc_sys_bytes gauge
go_memstats_gc_sys_bytes 4.614808e+06""";
InputStream inputStream = new ByteArrayInputStream(str.getBytes(StandardCharsets.UTF_8));
Map<String, MetricFamily> metricFamilyMap = OnlineParser.parseMetrics(inputStream);
assertNotNull(metricFamilyMap);
void parseMetrics2() {
try {
String str = """
# HELP go_gc_duration_seconds A summary of the pause duration of garbage collection cycles.
# TYPE go_gc_duration_seconds summary
go_gc_duration_seconds { quantile="0"} 2.0209e-05 321312
go_gc_duration_seconds{ quantile = "0.25" } 6.6917e-05
go_gc_duration_seconds{quantile="0.5"} -Inf
go_gc_duration_seconds{ quantile = "0.75"} +Inf
go_gc_duration_seconds{quantile="1"} NaN
go_gc_duration_seconds_sum 0.001134793 321314
go_gc_duration_seconds_count 5 43
# HELP go_goroutines Number of goroutines that currently exist.
# TYPE go_goroutines gauge
go_goroutines 32
# HELP go_info Information about the Go environment.
# TYPE go_info gauge
go_info{version="go1.21.6"} 1
# HELP go_memstats_alloc_bytes Number of bytes allocated and still in use.
# TYPE go_memstats_alloc_bytes gauge
go_memstats_alloc_bytes 1.5716224e+07
# HELP go_memstats_alloc_bytes_total Total number of bytes allocated, even if freed.
# TYPE go_memstats_alloc_bytes_total counter
go_memstats_alloc_bytes_total 2.0707544e+07
# HELP go_memstats_buck_hash_sys_bytes Number of bytes used by the profiling bucket hash table.
# TYPE go_memstats_buck_hash_sys_bytes gauge
go_memstats_buck_hash_sys_bytes 1.457881e+06
# HELP go_memstats_frees_total Total number of frees.
# TYPE go_memstats_frees_total counter
go_memstats_frees_total 50438
# HELP go_memstats_gc_sys_bytes Number of bytes used for garbage collection system metadata.
# TYPE go_memstats_gc_sys_bytes gauge
go_memstats_gc_sys_bytes 4.614808e+06""";
InputStream inputStream = new ByteArrayInputStream(str.getBytes());
Map<String, MetricFamily> metricFamilyMap = OnlineParser.parseMetrics(inputStream);
System.out.println(1);
}
catch (Exception e) {
e.printStackTrace();
}
}
}
@@ -18,7 +18,6 @@
package org.apache.hertzbeat.common.entity.alerter;
import static io.swagger.v3.oas.annotations.media.Schema.AccessMode.READ_ONLY;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.persistence.Column;
import jakarta.persistence.Convert;
@@ -98,12 +97,10 @@ public class GroupAlert {
@Schema(title = "This record creation time (millisecond timestamp)")
@CreatedDate
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime gmtCreate;
@Schema(title = "Record the latest modification time (timestamp in milliseconds)")
@LastModifiedDate
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime gmtUpdate;
@Transient
@@ -18,7 +18,6 @@
package org.apache.hertzbeat.common.entity.alerter;
import static io.swagger.v3.oas.annotations.media.Schema.AccessMode.READ_ONLY;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.persistence.Column;
import jakarta.persistence.Convert;
@@ -101,12 +100,10 @@ public class SingleAlert {
@Schema(title = "This record creation time (millisecond timestamp)")
@CreatedDate
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime gmtCreate;
@Schema(title = "Record the latest modification time (timestamp in milliseconds)")
@LastModifiedDate
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime gmtUpdate;
@Override
@@ -59,10 +59,4 @@ public class FtpProtocol implements CommonRequestProtocol, Protocol {
* Timeout
*/
private String timeout;
/**
* Whether ftp uses link encryption ssl/tls, i.e. ftp or sftp
*
*/
private String ssl = "false";
}
@@ -53,10 +53,5 @@
<version>${hertzbeat.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
@@ -56,7 +56,6 @@ public class HttpMonitorE2eTest extends AbstractCollectE2eTest {
private static final int MOCK_SERVER_PORT = 52376;
private static final String LOCALHOST = "127.0.0.1";
private static final String RELATIVE_PATH = "/";
private static final List<String> ALLOW_EMPTY_WHITE_LIST = List.of("header");
private static HttpServer mockServer;
@AfterAll
@@ -97,12 +96,7 @@ public class HttpMonitorE2eTest extends AbstractCollectE2eTest {
List<Map<String, Configmap>> configmapFromPreCollectData = new LinkedList<>();
for (Metrics metricsDef : dockerJob.getMetrics()) {
metricsDef = CollectUtil.replaceCryPlaceholderToMetrics(metricsDef, configmapFromPreCollectData.size() > 0 ? configmapFromPreCollectData.get(0) : new HashMap<>());
CollectRep.MetricsData metricsData;
if (ALLOW_EMPTY_WHITE_LIST.contains(metricsDef.getName())) {
metricsData = validateMetricsCollection(metricsDef, metricsDef.getName(), true);
} else {
metricsData = validateMetricsCollection(metricsDef, metricsDef.getName());
}
CollectRep.MetricsData metricsData = validateMetricsCollection(metricsDef, metricsDef.getName());
configmapFromPreCollectData = CollectUtil.getConfigmapFromPreCollectData(metricsData);
}
}
@@ -38,8 +38,6 @@ import org.testcontainers.lifecycle.Startables;
import org.testcontainers.utility.DockerImageName;
import java.time.Duration;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
@@ -56,7 +54,6 @@ public class SshCollectE2eTest extends AbstractCollectE2eTest {
private static final String ROOT_USER = "root";
private static final int SSH_PORT = 22;
private static final int PASSWORD_LENGTH = 12;
private static final List<String> ALLOW_EMPTY_WHITE_LIST = Arrays.asList("top_mem_process", "top_cpu_process");
private static GenericContainer<?> linuxContainer;
@@ -91,13 +88,8 @@ public class SshCollectE2eTest extends AbstractCollectE2eTest {
Assertions.assertTrue(linuxContainer.isRunning(), "Ubuntu container should be running");
Job ubuntuJob = appService.getAppDefine("ubuntu");
ubuntuJob.getMetrics().forEach(metricsDef -> {
if (ALLOW_EMPTY_WHITE_LIST.contains(metricsDef.getName())) {
validateMetricsCollection(metricsDef, metricsDef.getName(), true);
} else {
validateMetricsCollection(metricsDef, metricsDef.getName());
}
});
ubuntuJob.getMetrics().forEach(metricsDef ->
validateMetricsCollection(metricsDef, metricsDef.getName()));
}
@Override
@@ -1,128 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.collector.collect.basic.telnet;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.collector.collect.AbstractCollectE2eTest;
import org.apache.hertzbeat.collector.collect.telnet.TelnetCollectImpl;
import org.apache.hertzbeat.collector.util.CollectUtil;
import org.apache.hertzbeat.common.entity.job.Configmap;
import org.apache.hertzbeat.common.entity.job.Job;
import org.apache.hertzbeat.common.entity.job.Metrics;
import org.apache.hertzbeat.common.entity.job.protocol.Protocol;
import org.apache.hertzbeat.common.entity.job.protocol.TelnetProtocol;
import org.apache.hertzbeat.common.entity.message.CollectRep;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.wait.strategy.Wait;
import org.testcontainers.utility.DockerImageName;
import java.time.Duration;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
* Integration test for Zookeeper monitoring functionality
*/
@Slf4j
@ExtendWith(MockitoExtension.class)
public class ZookeeperMonitorE2eTest extends AbstractCollectE2eTest {
private static final String ZOOKEEPER_IMAGE_NAME = "zookeeper:3.8.4";
private static final String ZOOKEEPER_NAME = "zookeeper";
private static final Integer ZOOKEEPER_PORT = 2181;
private static GenericContainer<?> zookeeperContainer;
@AfterAll
public static void tearDown() {
if (zookeeperContainer != null) {
zookeeperContainer.stop();
}
}
@BeforeEach
public void setUp() throws Exception {
super.setUp();
collect = new TelnetCollectImpl();
metrics = new Metrics();
try {
// Start Zookeeper container with custom configuration
zookeeperContainer = new GenericContainer<>(DockerImageName.parse(ZOOKEEPER_IMAGE_NAME))
.withExposedPorts(ZOOKEEPER_PORT)
.withEnv("ZOO_4LW_COMMANDS_WHITELIST", "*")
.withNetworkAliases(ZOOKEEPER_NAME)
.waitingFor(
Wait.forLogMessage(".*Started AdminServer on address.*\\n", 1)
.withStartupTimeout(Duration.ofSeconds(60))
)
.withLogConsumer(outputFrame -> {
log.info(outputFrame.getUtf8String());
});
zookeeperContainer.start();
log.info("Zookeeper container started at {}:{}",
zookeeperContainer.getHost(),
zookeeperContainer.getMappedPort(ZOOKEEPER_PORT));
} catch (Exception e) {
e.printStackTrace();
log.error("Failed to start Zookeeper container", e);
throw e;
}
Thread.sleep(30000);
}
@Override
protected CollectRep.MetricsData.Builder collectMetrics(Metrics metricsDef) {
TelnetProtocol telnetProtocol = (TelnetProtocol) buildProtocol(metricsDef);
metrics.setTelnet(telnetProtocol);
CollectRep.MetricsData.Builder metricsData = CollectRep.MetricsData.newBuilder();
metricsData.setApp(ZOOKEEPER_NAME);
metrics.setAliasFields(metricsDef.getAliasFields());
return collectMetricsData(metrics, metricsDef, metricsData);
}
@Override
protected Protocol buildProtocol(Metrics metricsDef) {
TelnetProtocol protocol = new TelnetProtocol();
protocol.setHost(zookeeperContainer.getHost());
protocol.setPort(String.valueOf(zookeeperContainer.getMappedPort(ZOOKEEPER_PORT)));
protocol.setCmd(metricsDef.getTelnet().getCmd());
return protocol;
}
@Test
public void testZookeeperMonitor() {
Assertions.assertTrue(zookeeperContainer.isRunning(), "Zookeeper container should be running");
Job dockerJob = appService.getAppDefine("zookeeper");
List<Map<String, Configmap>> configmapFromPreCollectData = new LinkedList<>();
for (Metrics metricsDef : dockerJob.getMetrics()) {
metricsDef = CollectUtil.replaceCryPlaceholderToMetrics(metricsDef, configmapFromPreCollectData.size() > 0 ? configmapFromPreCollectData.get(0) : new HashMap<>());
CollectRep.MetricsData metricsData = validateMetricsCollection(metricsDef, metricsDef.getName());
configmapFromPreCollectData = CollectUtil.getConfigmapFromPreCollectData(metricsData);
}
}
}
@@ -22,7 +22,6 @@ import org.apache.hertzbeat.collector.dispatch.CollectDataDispatch;
import org.apache.hertzbeat.collector.dispatch.MetricsCollect;
import org.apache.hertzbeat.collector.dispatch.timer.Timeout;
import org.apache.hertzbeat.collector.dispatch.timer.WheelTimerTask;
import org.apache.hertzbeat.common.constants.CommonConstants;
import org.apache.hertzbeat.common.entity.job.Job;
import org.apache.hertzbeat.common.entity.job.Metrics;
import org.apache.hertzbeat.common.entity.job.protocol.Protocol;
@@ -78,39 +77,20 @@ public abstract class AbstractCollectE2eTest {
/**
* Validate metrics collection, check if the metrics values are not empty <br/>
* @param metricsDef metrics definition
* @param metricName metric name
* @return metrics data
* We believe that all monitoring metrics should have data
*/
protected CollectRep.MetricsData validateMetricsCollection(Metrics metricsDef, String metricName) {
// By default, we do not allow empty values
return validateMetricsCollection(metricsDef, metricName, false);
}
/**
* Validate metrics collection, check if the metrics values are not empty <br/>
* We believe that all monitoring metrics should have data
*
* @param metricsDef metrics definition
* @param metricName metric name
* @param allowEmpty In some special scenarios, it is not necessary to check if the value is `&nbsp;`
*/
protected CollectRep.MetricsData validateMetricsCollection(Metrics metricsDef, String metricName, boolean allowEmpty) {
CollectRep.MetricsData.Builder metricsData = collectMetrics(metricsDef);
metricsCollect.calculateFields(metricsDef, metricsData);
Assertions.assertTrue(metricsData.getValuesList().size() > 0,
String.format("%s metrics values should not be empty, detail: %s", metricName, metricsData.getMsg()));
String.format("%s metrics values should not be empty", metricName));
for (CollectRep.ValueRow valueRow : metricsData.getValuesList()) {
for (int i = 0; i < valueRow.getColumnsCount(); i++) {
Assertions.assertFalse(valueRow.getColumns(i).isEmpty(),
String.format("%s metric column %d should not be empty", metricName, i));
if (!allowEmpty) {
// Check if the value is not null
Assertions.assertNotEquals(CommonConstants.NULL_VALUE, valueRow.getColumns(i), String.format("%s metric column %d should not be null", metricName, i));
}
}
}
@@ -118,31 +98,21 @@ public abstract class AbstractCollectE2eTest {
return metricsData.build();
}
/**
* Set alias fields for metrics
*
* @param metrics metrics
* @param metricsDef metrics definition
*/
protected void setMetricsAliasFields(Metrics metrics, Metrics metricsDef) {
List<String> aliasFields = metricsDef.getAliasFields() == null
? metricsDef.getFields().stream().map(Metrics.Field::getField).collect(Collectors.toList())
: metricsDef.getAliasFields();
metrics.setAliasFields(aliasFields);
metricsDef.setAliasFields(aliasFields);
metrics.setAliasFields(metricsDef.getAliasFields() == null
? metricsDef.getFields().stream()
.map(Metrics.Field::getField)
.collect(Collectors.toList()) :
metricsDef.getAliasFields());
}
protected abstract CollectRep.MetricsData.Builder collectMetrics(Metrics metricsDef);
protected CollectRep.MetricsData.Builder collectMetricsData(Metrics metrics, Metrics metricsDef) {
CollectRep.MetricsData.Builder metricsData = CollectRep.MetricsData.newBuilder();
return this.collectMetricsData(metrics, metricsDef, metricsData);
}
protected CollectRep.MetricsData.Builder collectMetricsData(Metrics metrics, Metrics metricsDef, CollectRep.MetricsData.Builder metricsData) {
setMetricsAliasFields(metrics, metricsDef);
// Collect metrics
CollectRep.MetricsData.Builder metricsData = CollectRep.MetricsData.newBuilder();
collect.collect(metricsData, metrics);
return metricsData;
}
@@ -27,7 +27,6 @@ import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ImportRuntimeHints;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.scheduling.annotation.EnableAsync;
/**
* start up class.
@@ -40,7 +39,6 @@ import org.springframework.scheduling.annotation.EnableAsync;
@ComponentScan(basePackages = {"org.apache.hertzbeat"})
@ConfigurationPropertiesScan(basePackages = {"org.apache.hertzbeat"})
@ImportRuntimeHints(HertzbeatRuntimeHintsRegistrar.class)
@EnableAsync
public class Manager {
public static void main(String[] args) {
SpringApplication.run(Manager.class, args);
@@ -71,7 +71,7 @@ public class CollectorServiceImpl implements CollectorService {
Specification<Collector> specification = (root, query, criteriaBuilder) -> {
Predicate predicate = criteriaBuilder.conjunction();
if (StringUtils.isNotBlank(name)) {
Predicate predicateName = criteriaBuilder.like(criteriaBuilder.lower(root.get("name")), "%" + name.toLowerCase() + "%");
Predicate predicateName = criteriaBuilder.like(root.get("name"), "%" + name + "%");
predicate = criteriaBuilder.and(predicateName);
}
return predicate;
@@ -566,13 +566,11 @@ public class MonitorServiceImpl implements MonitorService {
List<Predicate> orList = new ArrayList<>();
if (StringUtils.isNotBlank(search)) {
Predicate predicateHost = criteriaBuilder.like(root.get("host"), "%" + search + "%");
Predicate predicateName = criteriaBuilder.like(criteriaBuilder.lower(root.get("name")), "%" + search.toLowerCase() + "%");
if (StringUtils.isNumeric(search)){
Predicate predicateId = criteriaBuilder.equal(root.get("id"), Long.parseLong(search));
orList.add(predicateId);
}
Predicate predicateName = criteriaBuilder.like(root.get("name"), "%" + search + "%");
Predicate predicateId = criteriaBuilder.like(root.get("id"), "%" + search + "%");
orList.add(predicateHost);
orList.add(predicateName);
orList.add(predicateId);
}
if (StringUtils.isNotBlank(labels)) {
String[] labelAres = labels.split(",");
@@ -92,9 +92,9 @@ public class TagServiceImpl implements TagService {
List<Predicate> orList = new ArrayList<>();
if (StringUtils.isNotBlank(search)) {
Predicate predicateName = criteriaBuilder.like(criteriaBuilder.lower(root.get("name")), "%" + search.toLowerCase() + "%");
Predicate predicateName = criteriaBuilder.like(root.get("name"), "%" + search + "%");
orList.add(predicateName);
Predicate predicateValue = criteriaBuilder.like(criteriaBuilder.lower(root.get("tagValue")), "%" + search.toLowerCase() + "%");
Predicate predicateValue = criteriaBuilder.like(root.get("tagValue"), "%" + search + "%");
orList.add(predicateValue);
}
Predicate[] orPredicates = new Predicate[orList.size()];
@@ -88,15 +88,6 @@ params:
range: '[0,100000]'
required: true
defaultValue: 1000
- field: ssl
# name-param field display i18n name
name:
zh-CN: 启用SFTP
en-US: SFTP
# type-param field type(most mapping the html input type)
type: boolean
# required-true or false
required: true
# collect metrics config list
metrics:
# metrics - basic
@@ -131,4 +122,3 @@ metrics:
password: ^_^password^_^
direction: ^_^direction^_^
timeout: ^_^timeout^_^
ssl: ^_^ssl^_^
@@ -14,7 +14,7 @@
# limitations under the License.
# The monitoring type categoryservice-application service monitoring db-database monitoring custom-custom monitoring os-operating system monitoring
category: llm
category: server
# The monitoring type eg: linux windows tomcat mysql aws...
app: nvidia
# The monitoring i18n name
@@ -72,7 +72,6 @@ resourceRole:
# eg: /api/v1/source3===get means /api/v1/source3===get can be access by anyone, no need auth.
excludedResource:
- /api/alerts/report/**===*
- /api/alert/sse/**===*
- /api/account/auth/**===*
- /api/i18n/**===get
- /api/apps/hierarchy===get
@@ -80,7 +79,6 @@ excludedResource:
- /api/status/page/public/**===*
# web ui resource
- /===get
- /assets/**===get
- /dashboard/**===get
- /monitors/**===get
- /alert/**===get
@@ -1,118 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.warehouse.db;
import java.net.URI;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.common.constants.NetworkConstants;
import org.apache.hertzbeat.common.constants.SignConstants;
import org.apache.hertzbeat.common.util.Base64Util;
import org.apache.hertzbeat.warehouse.store.history.greptime.GreptimeProperties;
import org.apache.hertzbeat.warehouse.store.history.vm.PromQlQueryContent;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
/**
* query executor for victor metrics
*/
@Component
@ConditionalOnProperty(prefix = "warehouse.store.greptime", name = "enabled", havingValue = "true")
@Slf4j
public class GreptimePromqlQueryExecutor implements QueryExecutor {
private static final String QUERY_PATH = "/v1/prometheus/api/v1/query";
private final GreptimeProperties greptimeProperties;
private final RestTemplate restTemplate;
public GreptimePromqlQueryExecutor(GreptimeProperties greptimeProperties, RestTemplate restTemplate) {
this.greptimeProperties = greptimeProperties;
this.restTemplate = restTemplate;
}
@Override
public List<Map<String, Object>> execute(String query) {
// http run the promql query
List<Map<String, Object>> results = new LinkedList<>();
try {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setAccept(List.of(MediaType.APPLICATION_JSON));
if (StringUtils.hasText(greptimeProperties.username())
&& StringUtils.hasText(greptimeProperties.password())) {
String authStr = greptimeProperties.username() + ":" + greptimeProperties.password();
String encodedAuth = Base64Util.encode(authStr);
headers.add(HttpHeaders.AUTHORIZATION, NetworkConstants.BASIC + SignConstants.BLANK + encodedAuth);
}
HttpEntity<Void> httpEntity = new HttpEntity<>(headers);
URI uri = UriComponentsBuilder.fromHttpUrl(greptimeProperties.httpEndpoint() + QUERY_PATH)
.queryParam("query", URLEncoder.encode(query, StandardCharsets.UTF_8))
.build(true).toUri();
ResponseEntity<PromQlQueryContent> responseEntity = restTemplate.exchange(uri,
HttpMethod.GET, httpEntity, PromQlQueryContent.class);
if (responseEntity.getStatusCode().is2xxSuccessful()) {
if (responseEntity.getBody() != null && responseEntity.getBody().getData() != null
&& responseEntity.getBody().getData().getResult() != null) {
List<PromQlQueryContent.ContentData.Content> contents = responseEntity.getBody().getData().getResult();
for (PromQlQueryContent.ContentData.Content content : contents) {
Map<String, String> labels = content.getMetric();
Map<String, Object> queryResult = new HashMap<>(8);
queryResult.putAll(labels);
if (content.getValue() != null && content.getValue().length == 2) {
queryResult.put("__timestamp__", content.getValue()[0]);
queryResult.put("__value__", content.getValue()[1]);
} else if (content.getValues() != null && !content.getValues().isEmpty()) {
List<Object> values = new LinkedList<>();
for (Object[] valueArr : content.getValues()) {
values.add(valueArr[1]);
}
queryResult.put("__value__", values);
}
results.add(queryResult);
}
}
} else {
log.error("query metrics data from greptime failed. {}", responseEntity);
}
} catch (Exception e) {
log.error(e.toString(), e);
}
return results;
}
@Override
public boolean support(String datasource) {
return "promql".equals(datasource);
}
}
@@ -1,31 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.warehouse.db;
import java.util.List;
import java.util.Map;
/**
* query executor interface
*/
public interface QueryExecutor {
List<Map<String, Object>> execute(String query);
boolean support(String datasource);
}
@@ -1,118 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.warehouse.db;
import java.net.URI;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.common.constants.NetworkConstants;
import org.apache.hertzbeat.common.constants.SignConstants;
import org.apache.hertzbeat.common.util.Base64Util;
import org.apache.hertzbeat.warehouse.store.history.vm.PromQlQueryContent;
import org.apache.hertzbeat.warehouse.store.history.vm.VictoriaMetricsProperties;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
/**
* query executor for victor metrics
*/
@Component
@ConditionalOnProperty(prefix = "warehouse.store.victoria-metrics", name = "enabled", havingValue = "true")
@Slf4j
public class VictoriaMetricsQueryExecutor implements QueryExecutor {
private static final String QUERY_PATH = "/api/v1/query";
private final VictoriaMetricsProperties victoriaMetricsProp;
private final RestTemplate restTemplate;
public VictoriaMetricsQueryExecutor(VictoriaMetricsProperties victoriaMetricsProp, RestTemplate restTemplate) {
this.victoriaMetricsProp = victoriaMetricsProp;
this.restTemplate = restTemplate;
}
@Override
public List<Map<String, Object>> execute(String query) {
// http run the promql query
List<Map<String, Object>> results = new LinkedList<>();
try {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setAccept(List.of(MediaType.APPLICATION_JSON));
if (StringUtils.hasText(victoriaMetricsProp.username())
&& StringUtils.hasText(victoriaMetricsProp.password())) {
String authStr = victoriaMetricsProp.username() + ":" + victoriaMetricsProp.password();
String encodedAuth = Base64Util.encode(authStr);
headers.add(HttpHeaders.AUTHORIZATION, NetworkConstants.BASIC + SignConstants.BLANK + encodedAuth);
}
HttpEntity<Void> httpEntity = new HttpEntity<>(headers);
URI uri = UriComponentsBuilder.fromHttpUrl(victoriaMetricsProp.url() + QUERY_PATH)
.queryParam("query", URLEncoder.encode(query, StandardCharsets.UTF_8))
.build(true).toUri();
ResponseEntity<PromQlQueryContent> responseEntity = restTemplate.exchange(uri,
HttpMethod.GET, httpEntity, PromQlQueryContent.class);
if (responseEntity.getStatusCode().is2xxSuccessful()) {
if (responseEntity.getBody() != null && responseEntity.getBody().getData() != null
&& responseEntity.getBody().getData().getResult() != null) {
List<PromQlQueryContent.ContentData.Content> contents = responseEntity.getBody().getData().getResult();
for (PromQlQueryContent.ContentData.Content content : contents) {
Map<String, String> labels = content.getMetric();
Map<String, Object> queryResult = new HashMap<>(8);
queryResult.putAll(labels);
if (content.getValue() != null && content.getValue().length == 2) {
queryResult.put("__timestamp__", content.getValue()[0]);
queryResult.put("__value__", content.getValue()[1]);
} else if (content.getValues() != null && !content.getValues().isEmpty()) {
List<Object> values = new LinkedList<>();
for (Object[] valueArr : content.getValues()) {
values.add(valueArr[1]);
}
queryResult.put("__value__", values);
}
results.add(queryResult);
}
}
} else {
log.error("query metrics data from victor-metrics failed. {}", responseEntity);
}
} catch (Exception e) {
log.error(e.toString(), e);
}
return results;
}
@Override
public boolean support(String datasource) {
return "promql".equals(datasource);
}
}
@@ -18,11 +18,8 @@
package org.apache.hertzbeat.warehouse.store;
import java.util.Optional;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.common.constants.CommonConstants;
import org.apache.hertzbeat.common.entity.manager.Monitor;
import org.apache.hertzbeat.common.entity.message.CollectRep;
import org.apache.hertzbeat.common.queue.CommonDataQueue;
import org.apache.hertzbeat.plugin.PostCollectPlugin;
@@ -30,6 +27,7 @@ import org.apache.hertzbeat.plugin.runner.PluginRunner;
import org.apache.hertzbeat.warehouse.WarehouseWorkerPool;
import org.apache.hertzbeat.warehouse.store.history.HistoryDataWriter;
import org.apache.hertzbeat.warehouse.store.realtime.RealTimeDataWriter;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
@@ -46,8 +44,6 @@ public class DataStorageDispatch {
private final RealTimeDataWriter realTimeDataWriter;
private final Optional<HistoryDataWriter> historyDataWriter;
private final PluginRunner pluginRunner;
@PersistenceContext
private EntityManager entityManager;
public DataStorageDispatch(CommonDataQueue commonDataQueue,
WarehouseWorkerPool workerPool,
@@ -91,16 +87,24 @@ public class DataStorageDispatch {
if (metricsData.getPriority() == 0) {
long id = metricsData.getId();
CollectRep.Code code = metricsData.getCode();
// query current status
String queryStatusSql = "SELECT status FROM hzb_monitor WHERE id = ?";
try {
String sql = "UPDATE hzb_monitor SET status = ? WHERE id = ? AND status != ?";
int status = code == CollectRep.Code.SUCCESS ? CommonConstants.MONITOR_UP_CODE : CommonConstants.MONITOR_DOWN_CODE;
int matchedRows = jdbcTemplate.update(sql, status, id, status);
if (matchedRows > 0) {
entityManager.getEntityManagerFactory().getCache().evict(Monitor.class, id);
int currentStatus = jdbcTemplate.queryForObject(queryStatusSql, Integer.class, id);
if (code == CollectRep.Code.SUCCESS && currentStatus == CommonConstants.MONITOR_DOWN_CODE) {
// if collect success and current status is DOWN, update to UP
String sql = "UPDATE hzb_monitor SET status = ? WHERE id = ?";
jdbcTemplate.update(sql, CommonConstants.MONITOR_UP_CODE, id);
} else if (code != CollectRep.Code.SUCCESS && currentStatus == CommonConstants.MONITOR_UP_CODE) {
// if collect failed and current status is UP, update to DOWN
String sql = "UPDATE hzb_monitor SET status = ? WHERE id = ?";
jdbcTemplate.update(sql, CommonConstants.MONITOR_DOWN_CODE, id);
}
} catch (Exception e) {
log.error("Update monitor status failed for monitor id: {}", id, e);
} catch (EmptyResultDataAccessException ignored) {
// when query currentStatus result is null
}
}
}
}
+2 -3
View File
@@ -352,9 +352,8 @@ The text of each license is the standard Apache 2.0 license.
https://mvnrepository.com/artifact/org.apache.rocketmq/rocketmq-remoting/4.9.4 Apache-2.0
https://mvnrepository.com/artifact/org.apache.rocketmq/rocketmq-srvutil/4.9.4 Apache-2.0
https://mvnrepository.com/artifact/org.apache.rocketmq/rocketmq-tools/4.9.4 Apache-2.0
https://mvnrepository.com/artifact/org.apache.sshd/sshd-common/2.13.1 Apache-2.0
https://mvnrepository.com/artifact/org.apache.sshd/sshd-core/2.13.1 Apache-2.0
https://mvnrepository.com/artifact/org.apache.sshd/sshd-sftp/2.13.1 Apache-2.0
https://mvnrepository.com/artifact/org.apache.sshd/sshd-common/2.8.0 Apache-2.0
https://mvnrepository.com/artifact/org.apache.sshd/sshd-core/2.8.0 Apache-2.0
https://mvnrepository.com/artifact/org.apache.tomcat.embed/tomcat-embed-el/10.1.19 Apache-2.0
https://mvnrepository.com/artifact/org.apache.tomcat.embed/tomcat-embed-websocket/10.1.19 Apache-2.0
https://mvnrepository.com/artifact/org.apache.xmlbeans/xmlbeans/3.1.0 Apache-2.0
+2 -3
View File
@@ -352,9 +352,8 @@ The text of each license is the standard Apache 2.0 license.
https://mvnrepository.com/artifact/org.apache.rocketmq/rocketmq-remoting/4.9.4 Apache-2.0
https://mvnrepository.com/artifact/org.apache.rocketmq/rocketmq-srvutil/4.9.4 Apache-2.0
https://mvnrepository.com/artifact/org.apache.rocketmq/rocketmq-tools/4.9.4 Apache-2.0
https://mvnrepository.com/artifact/org.apache.sshd/sshd-common/2.13.1 Apache-2.0
https://mvnrepository.com/artifact/org.apache.sshd/sshd-core/2.13.1 Apache-2.0
https://mvnrepository.com/artifact/org.apache.sshd/sshd-sftp/2.13.1 Apache-2.0
https://mvnrepository.com/artifact/org.apache.sshd/sshd-common/2.8.0 Apache-2.0
https://mvnrepository.com/artifact/org.apache.sshd/sshd-core/2.8.0 Apache-2.0
https://mvnrepository.com/artifact/org.apache.tomcat.embed/tomcat-embed-el/10.1.19 Apache-2.0
https://mvnrepository.com/artifact/org.apache.tomcat.embed/tomcat-embed-websocket/10.1.19 Apache-2.0
https://mvnrepository.com/artifact/org.apache.xmlbeans/xmlbeans/3.1.0 Apache-2.0
+2 -3
View File
@@ -284,9 +284,8 @@ The text of each license is the standard Apache 2.0 license.
https://mvnrepository.com/artifact/org.apache.rocketmq/rocketmq-remoting/4.9.4 Apache-2.0
https://mvnrepository.com/artifact/org.apache.rocketmq/rocketmq-srvutil/4.9.4 Apache-2.0
https://mvnrepository.com/artifact/org.apache.rocketmq/rocketmq-tools/4.9.4 Apache-2.0
https://mvnrepository.com/artifact/org.apache.sshd/sshd-common/2.13.1 Apache-2.0
https://mvnrepository.com/artifact/org.apache.sshd/sshd-core/2.13.1 Apache-2.0
https://mvnrepository.com/artifact/org.apache.sshd/sshd-sftp/2.13.1 Apache-2.0
https://mvnrepository.com/artifact/org.apache.sshd/sshd-common/2.8.0 Apache-2.0
https://mvnrepository.com/artifact/org.apache.sshd/sshd-core/2.8.0 Apache-2.0
https://mvnrepository.com/artifact/org.apache.tomcat.embed/tomcat-embed-el/10.1.19 Apache-2.0
https://mvnrepository.com/artifact/org.apache.tomcat.embed/tomcat-embed-websocket/10.1.19 Apache-2.0
https://mvnrepository.com/artifact/org.apache.plc4x/plc4j-api/0.12.0 Apache-2.0
-6
View File
@@ -174,7 +174,6 @@
<mysql-jdbcdriver.version>8.0.33</mysql-jdbcdriver.version>
<arrow.version>18.1.0</arrow.version>
<snappy-java.version>1.1.10.7</snappy-java.version>
<sshd-sftp.version>2.13.1</sshd-sftp.version>
</properties>
<dependencyManagement>
@@ -475,11 +474,6 @@
<artifactId>arrow-memory-netty</artifactId>
<version>${arrow.version}</version>
</dependency>
<dependency>
<groupId>org.apache.sshd</groupId>
<artifactId>sshd-sftp</artifactId>
<version>${sshd-sftp.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
@@ -72,7 +72,6 @@ resourceRole:
# eg: /api/v1/source3===get means /api/v1/source3===get can be access by anyone, no need auth.
excludedResource:
- /api/alerts/report/**===*
- /api/alert/sse/**===*
- /api/account/auth/**===*
- /api/i18n/**===get
- /api/apps/hierarchy===get
@@ -80,7 +79,6 @@ excludedResource:
- /api/status/page/public/**===*
# web ui resource
- /===get
- /assets/**===get
- /dashboard/**===get
- /monitors/**===get
- /alert/**===get
@@ -72,7 +72,6 @@ resourceRole:
# eg: /api/v1/source3===get means /api/v1/source3===get can be access by anyone, no need auth.
excludedResource:
- /api/alerts/report/**===*
- /api/alert/sse/**===*
- /api/account/auth/**===*
- /api/i18n/**===get
- /api/apps/hierarchy===get
@@ -80,7 +79,6 @@ excludedResource:
- /api/status/page/public/**===*
# web ui resource
- /===get
- /assets/**===get
- /dashboard/**===get
- /monitors/**===get
- /alert/**===get
@@ -72,7 +72,6 @@ resourceRole:
# eg: /api/v1/source3===get means /api/v1/source3===get can be access by anyone, no need auth.
excludedResource:
- /api/alerts/report/**===*
- /api/alert/sse/**===*
- /api/account/auth/**===*
- /api/i18n/**===get
- /api/apps/hierarchy===get
@@ -80,7 +79,6 @@ excludedResource:
- /api/status/page/public/**===*
# web ui resource
- /===get
- /assets/**===get
- /dashboard/**===get
- /monitors/**===get
- /alert/**===get
@@ -72,7 +72,6 @@ resourceRole:
# eg: /api/v1/source3===get means /api/v1/source3===get can be access by anyone, no need auth.
excludedResource:
- /api/alerts/report/**===*
- /api/alert/sse/**===*
- /api/account/auth/**===*
- /api/i18n/**===get
- /api/apps/hierarchy===get
@@ -80,7 +79,6 @@ excludedResource:
- /api/status/page/public/**===*
# web ui resource
- /===get
- /assets/**===get
- /dashboard/**===get
- /monitors/**===get
- /alert/**===get
-2
View File
@@ -72,7 +72,6 @@ resourceRole:
# eg: /api/v1/source3===get means /api/v1/source3===get can be access by anyone, no need auth.
excludedResource:
- /api/alerts/report/**===*
- /api/alert/sse/**===*
- /api/account/auth/**===*
- /api/i18n/**===get
- /api/apps/hierarchy===get
@@ -80,7 +79,6 @@ excludedResource:
- /api/status/page/public/**===*
# web ui resource
- /===get
- /assets/**===get
- /dashboard/**===get
- /monitors/**===get
- /alert/**===get
+6
View File
@@ -19,6 +19,7 @@ export class AppComponent implements OnInit {
private router: Router,
private titleSrv: TitleService,
private modalSrv: NzModalService,
private themeService: ThemeService,
@Inject(ALAIN_I18N_TOKEN) private i18nSvc: I18NService
) {
renderer.setAttribute(el.nativeElement, 'ng-alain-version', VERSION_ALAIN.full);
@@ -48,5 +49,10 @@ export class AppComponent implements OnInit {
this.modalSrv.closeAll();
}
});
// set theme
const storedTheme = localStorage.getItem('theme');
if (storedTheme) {
this.themeService.changeTheme(storedTheme);
}
}
}
+3 -13
View File
@@ -2,7 +2,6 @@ import { Platform } from '@angular/cdk/platform';
import { registerLocaleData } from '@angular/common';
import { HttpHeaders } from '@angular/common/http';
import ngEn from '@angular/common/locales/en';
import ngJa from '@angular/common/locales/ja';
import ngZh from '@angular/common/locales/zh';
import ngZhTw from '@angular/common/locales/zh-Hant';
import { Injectable } from '@angular/core';
@@ -13,13 +12,12 @@ import {
en_US as delonEnUS,
SettingsService,
zh_CN as delonZhCn,
zh_TW as delonZhTw,
ja_JP as delonJaJP
zh_TW as delonZhTw
} from '@delon/theme';
import { AlainConfigService } from '@delon/util/config';
import { enUS as dfEn, zhCN as dfZhCn, zhTW as dfZhTw, ja as dfJa } from 'date-fns/locale';
import { enUS as dfEn, zhCN as dfZhCn, zhTW as dfZhTw } from 'date-fns/locale';
import { NzSafeAny } from 'ng-zorro-antd/core/types';
import { en_US as zorroEnUS, NzI18nService, zh_CN as zorroZhCN, zh_TW as zorroZhTW, ja_JP as zorroJaJP } from 'ng-zorro-antd/i18n';
import { en_US as zorroEnUS, NzI18nService, zh_CN as zorroZhCN, zh_TW as zorroZhTW } from 'ng-zorro-antd/i18n';
import { Observable, zip } from 'rxjs';
import { map } from 'rxjs/operators';
@@ -59,14 +57,6 @@ const LANGS: { [key: string]: LangConfigData } = {
date: dfZhTw,
delon: delonZhTw,
abbr: '🇭🇰'
},
'ja-JP': {
text: '日本語',
ng: ngJa,
zorro: zorroJaJP,
date: dfJa,
delon: delonJaJP,
abbr: '🇯🇵'
}
};
@@ -12,7 +12,6 @@ import { catchError, map } from 'rxjs/operators';
import { ICONS } from '../../../style-icons';
import { ICONS_AUTO } from '../../../style-icons-auto';
import { MemoryStorageService } from '../../service/memory-storage.service';
import { ThemeService } from '../../service/theme.service';
import { I18NService } from '../i18n/i18n.service';
@Injectable({
@@ -29,8 +28,7 @@ export class StartupService {
@Inject(DA_SERVICE_TOKEN) private tokenService: ITokenService,
private httpClient: HttpClient,
private router: Router,
private storageService: MemoryStorageService,
private themeService: ThemeService
private storageService: MemoryStorageService
) {
iconSrv.addIcon(...ICONS_AUTO, ...ICONS);
}
@@ -88,7 +86,6 @@ export class StartupService {
this.storageService.putData('hierarchy', menuData.data);
this.menuService.resume();
this.titleService.suffix = appData.app.name;
this.themeService.changeTheme(null);
})
);
}
@@ -41,6 +41,9 @@ import { CONSTANTS } from '../../shared/constants';
<div nz-menu-item>
<header-fullscreen></header-fullscreen>
</div>
<div nz-menu-item>
<header-clear-storage></header-clear-storage>
</div>
<div nz-menu-item routerLink="/setting/labels">
<i nz-icon nzType="tag" class="mr-sm"></i>
<span style="margin-left: 4px">{{ 'menu.advanced.labels' | i18n }}</span>
@@ -0,0 +1,35 @@
import { ChangeDetectionStrategy, Component, HostListener, Inject } from '@angular/core';
import { I18NService } from '@core';
import { ALAIN_I18N_TOKEN } from '@delon/theme';
import { NzMessageService } from 'ng-zorro-antd/message';
import { NzModalService } from 'ng-zorro-antd/modal';
@Component({
selector: 'header-clear-storage',
template: `
<i nz-icon class="mr-sm" nzType="tool"></i>
{{ 'menu.clear.local.storage' | i18n }}
`,
host: {
'[class.d-block]': 'true'
},
changeDetection: ChangeDetectionStrategy.OnPush
})
export class HeaderClearStorageComponent {
constructor(
private modalSrv: NzModalService,
private messageSrv: NzMessageService,
@Inject(ALAIN_I18N_TOKEN) private i18nSvc: I18NService
) {}
@HostListener('click')
_click(): void {
this.modalSrv.confirm({
nzTitle: this.i18nSvc.fanyi('common.confirm.clear-cache'),
nzOnOk: () => {
localStorage.clear();
this.messageSrv.success(this.i18nSvc.fanyi('common.notify.clear-success'));
}
});
}
}
@@ -7,7 +7,6 @@ import { NzNotificationService } from 'ng-zorro-antd/notification';
import { finalize } from 'rxjs/operators';
import { Mute } from '../../../pojo/Mute';
import { SingleAlert } from '../../../pojo/SingleAlert';
import { AlertSoundService } from '../../../service/alert-sound.service';
import { AlertService } from '../../../service/alert.service';
import { GeneralConfigService } from '../../../service/general-config.service';
@@ -123,7 +122,6 @@ export class HeaderNotifyComponent implements OnInit, OnDestroy {
private previousCount = 0;
// default to mute status
mute: Mute = { mute: true };
private eventSource!: EventSource;
constructor(
private router: Router,
@Inject(ALAIN_I18N_TOKEN) private i18nSvc: I18NService,
@@ -156,7 +154,9 @@ export class HeaderNotifyComponent implements OnInit, OnDestroy {
}
);
this.loadData();
this.initSSEConnection();
this.refreshInterval = setInterval(() => {
this.loadData();
}, 10000); // every 10 seconds refresh the tabs
}
ngOnDestroy() {
@@ -207,6 +207,7 @@ export class HeaderNotifyComponent implements OnInit, OnDestroy {
let item = {
id: alert.id,
avatar: '/assets/img/notification.svg',
// title: `${alert.tags?.monitorName}--${this.i18nSvc.fanyi(`alert.severity.${alert.severity}`)}`,
title: alert.content,
datetime: new Date(alert.activeAt).toLocaleString(),
color: 'blue',
@@ -216,6 +217,11 @@ export class HeaderNotifyComponent implements OnInit, OnDestroy {
list.push(item);
});
this.data = this.updateNoticeData(list);
if (page.totalElements > this.previousCount && !this.mute.mute) {
this.alertSound.playAlertSound(this.i18nSvc.currentLang);
}
this.previousCount = page.totalElements;
this.count = page.totalElements;
} else {
console.warn(message.msg);
@@ -285,36 +291,4 @@ export class HeaderNotifyComponent implements OnInit, OnDestroy {
}
});
}
private initSSEConnection(): void {
const sseUrl = '/api/alert/sse/subscribe';
this.eventSource = new EventSource(sseUrl);
this.eventSource.addEventListener('ALERT_EVENT', (evt: MessageEvent) => {
let list: any[] = [];
let alert: SingleAlert = JSON.parse(evt.data);
let item = {
id: alert.id,
avatar: '/assets/img/notification.svg',
// title: `${alert.tags?.monitorName}--${this.i18nSvc.fanyi(`alert.severity.${alert.severity}`)}`,
title: alert.content,
datetime: new Date(alert.activeAt).toLocaleString(),
color: 'blue',
status: alert.status,
type: this.i18nSvc.fanyi('dashboard.alerts.title-no')
};
list.push(item);
this.data = this.updateNoticeData(list);
if (!this.mute.mute) {
this.alertSound.playAlertSound(this.i18nSvc.currentLang);
}
this.cdr.detectChanges();
});
this.eventSource.onerror = error => {
console.error('SSE connection error:', error);
setTimeout(() => this.initSSEConnection(), 3000);
};
}
}
@@ -129,7 +129,12 @@ export class HeaderUserComponent {
}
logout(): void {
this.localStorageSvc.clearAuthorization();
let tmp = this.localStorageSvc.getData(this.notShowAgainKey);
if (tmp === null) {
tmp = 'false';
}
this.localStorageSvc.clear();
this.localStorageSvc.putData(this.notShowAgainKey, tmp);
this.router.navigateByUrl('/passport/login');
}
+2
View File
@@ -19,6 +19,7 @@ import { NzInputModule } from 'ng-zorro-antd/input';
import { NzSpinModule } from 'ng-zorro-antd/spin';
import { LayoutBasicComponent } from './basic/basic.component';
import { HeaderClearStorageComponent } from './basic/widgets/clear-storage.component';
import { HeaderFullScreenComponent } from './basic/widgets/fullscreen.component';
import { HeaderI18nComponent } from './basic/widgets/i18n.component';
import { HeaderSearchComponent } from './basic/widgets/search.component';
@@ -32,6 +33,7 @@ const HEADER_COMPONENTS = [
HeaderSearchComponent,
HeaderFullScreenComponent,
HeaderI18nComponent,
HeaderClearStorageComponent,
HeaderUserComponent,
HeaderNotifyComponent
];
@@ -29,6 +29,10 @@
<app-toolbar>
<ng-template #center>
<div class="center-content">
<button nz-button (click)="sync()" nz-tooltip [nzTooltipTitle]="'common.refresh' | i18n">
<i nz-icon nzType="sync" nzTheme="outline"></i>
</button>
<div class="search-wrapper">
<nz-input-group [nzPrefix]="prefixTemplate" class="search-input">
<input
@@ -59,13 +63,7 @@
</app-toolbar>
<div class="alert-cards">
<nz-card
*ngFor="let group of groupAlerts"
class="alert-card"
[class.new-alert]="group.isNew"
[class]="'status-' + group.status"
[nzBordered]="false"
>
<nz-card *ngFor="let group of groupAlerts" class="alert-card" [class]="'status-' + group.status" [nzBordered]="false">
<!-- Alert Group Header -->
<div class="alert-header">
<div class="alert-info">
@@ -1,5 +1,4 @@
@import "~src/styles/theme";
/* 调整工具栏布局 */
:host ::ng-deep app-toolbar {
.center-content {
display: flex;
@@ -69,40 +68,17 @@
flex-direction: column;
gap: 12px;
padding: 16px;
transform-style: preserve-3d;
perspective: 1200px;
}
.alert-card {
position: relative;
background: @common-background-color;
background: #fff;
border-radius: 4px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
border-left: 4px solid #ff4d4f;
transition: all 0.3s;
z-index: 1;
&.expanded {
z-index: 2;
}
&.status-firing {
border-left: 4px solid #ff4d4f;
opacity: 0.9;
}
&.status-resolved {
border-left: 4px solid #52c41a;
opacity: 0.7;
}
&.status-pending {
border-left: 4px solid #faad14;
opacity: 0.8;
}
&:hover {
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.15);
z-index: 3;
}
::ng-deep .ant-card-body {
@@ -114,7 +90,7 @@
justify-content: space-between;
align-items: flex-start;
padding-bottom: 12px;
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
border-bottom: 1px solid #f0f0f0;
.alert-info {
flex: 1;
@@ -134,7 +110,7 @@
display: flex;
gap: 16px;
margin-top: 8px;
color: rgba(0, 0, 0, 0.5);
color: #8c8c8c;
font-size: 12px;
i {
@@ -167,22 +143,16 @@
.alert-details {
margin-top: 12px;
position: relative;
z-index: 4;
::ng-deep {
.ant-collapse {
background: transparent;
border: none;
position: relative;
z-index: 5;
.ant-collapse-item {
border-radius: 2px;
border: 1px solid rgba(0, 0, 0, 0.1);
border: 1px solid #f0f0f0;
margin-bottom: 8px;
position: relative;
z-index: 6;
&:last-child {
margin-bottom: 0;
@@ -192,11 +162,9 @@
padding: 8px 12px;
align-items: center;
cursor: pointer;
position: relative;
z-index: 7;
&:hover {
background-color: rgba(0, 0, 0, 0.05);
background-color: #fafafa;
}
.ant-collapse-header-text {
@@ -205,20 +173,19 @@
.ant-collapse-extra {
margin: 0;
color: rgba(0, 0, 0, 0.5);
color: #8c8c8c;
font-size: 12px;
}
.alert-content {
font-size: 13px;
color: #333;
line-height: 1.5;
}
}
.ant-collapse-content {
border-top: 1px solid rgba(0, 0, 0, 0.1);
position: relative;
z-index: 6;
border-top: 1px solid #f0f0f0;
.ant-collapse-content-box {
padding: 12px;
@@ -302,94 +269,16 @@
text-align: right;
}
@keyframes slideInFromRight {
0% {
transform: translate3d(120%, 0, 0) scale(0.95) rotate(3deg);
opacity: 0;
filter: blur(2px);
box-shadow: 0 24px 48px -12px rgba(0, 0, 0, 0.18);
}
50% {
opacity: 1;
filter: blur(0);
transform: translate3d(-5%, 0, 0) scale(1) rotate(-1deg);
}
75% {
transform: translate3d(2%, 0, 0) scale(1) rotate(0.5deg);
}
100% {
transform: translate3d(0, 0, 0) scale(1) rotate(0deg);
box-shadow: 0 8px 16px -4px rgba(0, 0, 0, 0.1);
}
}
.alert-card {
transition:
transform 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94),
opacity 0.5s ease-out,
box-shadow 0.3s ease;
will-change: transform, opacity;
&:hover {
transform: translateY(-2px) scale(1.005);
box-shadow: 0 12px 24px -8px rgba(0, 0, 0, 0.15);
&.status-firing {
border-left-color: #ff4d4f;
}
&::after {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(
90deg,
rgba(255, 255, 255, 0) 0%,
rgba(255, 255, 255, 0.3) 50%,
rgba(255, 255, 255, 0) 100%
);
opacity: 0;
animation: slideGlow 1s cubic-bezier(0.4, 0, 0.2, 1);
pointer-events: none;
&.status-resolved {
border-left-color: #52c41a;
}
&.new-alert {
animation:
slideInFromRight 0.8s cubic-bezier(0.34, 1.56, 0.64, 1) forwards,
cardLanding 0.6s 0.3s ease-out forwards;
}
}
@keyframes slideGlow {
0% {
transform: translateX(-100%);
opacity: 0;
}
50% {
opacity: 0.6;
}
100% {
transform: translateX(100%);
opacity: 0;
}
}
@keyframes cardLanding {
0% { transform: translateY(0); }
50% { transform: translateY(-8px); }
80% { transform: translateY(2px); }
100% { transform: translateY(0); }
}
.alert-card:nth-child(1) { animation-delay: 0.1s; }
.alert-card:nth-child(2) { animation-delay: 0.15s; }
.alert-card:nth-child(3) { animation-delay: 0.2s; }
.alert-card:nth-child(n+4) { animation-delay: 0.25s; }
[data-theme='dark'] {
:host {
.alert-card {
background-color: @common-background-color-dark;
}
&.status-pending {
border-left-color: #faad14;
}
}
@@ -17,7 +17,7 @@
* under the License.
*/
import { Component, Inject, OnDestroy, OnInit } from '@angular/core';
import { Component, Inject, OnInit } from '@angular/core';
import { I18NService } from '@core';
import { ALAIN_I18N_TOKEN } from '@delon/theme';
import { NzModalService } from 'ng-zorro-antd/modal';
@@ -26,15 +26,12 @@ import { NzNotificationService } from 'ng-zorro-antd/notification';
import { GroupAlert } from '../../../pojo/GroupAlert';
import { AlertService } from '../../../service/alert.service';
interface ExtendedGroupAlert extends GroupAlert {
isNew?: boolean;
}
@Component({
selector: 'app-alert-center',
templateUrl: './alert-center.component.html',
styleUrl: './alert-center.component.less'
})
export class AlertCenterComponent implements OnInit, OnDestroy {
export class AlertCenterComponent implements OnInit {
constructor(
private notifySvc: NzNotificationService,
private modal: NzModalService,
@@ -45,109 +42,18 @@ export class AlertCenterComponent implements OnInit, OnDestroy {
pageIndex: number = 1;
pageSize: number = 8;
total: number = 0;
groupAlerts: ExtendedGroupAlert[] = [];
groupAlerts!: GroupAlert[];
tableLoading: boolean = false;
checkedAlertIds = new Set<number>();
filterStatus!: string;
filterContent: string | undefined;
private eventSource!: EventSource;
ngOnInit(): void {
this.loadAlertsTable();
this.initSSESubscription();
}
ngOnDestroy(): void {
if (this.eventSource) {
this.eventSource.close();
}
}
// Initialize SSE subscription for real-time alerts
private initSSESubscription(): void {
this.eventSource = new EventSource('/api/alert/sse/subscribe');
this.eventSource.addEventListener('ALERT_EVENT', (evt: MessageEvent) => {
try {
const newAlert: GroupAlert = JSON.parse(evt.data);
this.updateAlertList(newAlert);
} catch (error) {
console.error('Error parsing SSE data:', error);
}
});
// Handle SSE errors
this.eventSource.onerror = error => {
console.error('SSE connection error:', error);
this.eventSource.close();
};
}
private updateAlertList(newAlert: GroupAlert): void {
const extendedAlert: ExtendedGroupAlert = {
...newAlert,
isNew: true
};
if (!extendedAlert.alerts) {
extendedAlert.alerts = [];
}
const matchesFilter = this.checkAlertMatchesFilter(extendedAlert);
if (!matchesFilter) {
return;
}
const existingIndex = this.groupAlerts.findIndex(a => a.id === extendedAlert.id);
if (existingIndex === -1) {
this.groupAlerts = [extendedAlert, ...this.groupAlerts];
this.total += 1;
setTimeout(() => {
const index = this.groupAlerts.findIndex(a => a.id === extendedAlert.id);
if (index !== -1) {
this.groupAlerts[index].isNew = false;
// 触发变更检测
this.groupAlerts = [...this.groupAlerts];
}
}, 1000);
} else {
this.groupAlerts[existingIndex] = {
...extendedAlert,
isNew: true
};
setTimeout(() => {
if (this.groupAlerts[existingIndex]) {
this.groupAlerts[existingIndex].isNew = false;
this.groupAlerts = [...this.groupAlerts];
}
}, 1000);
this.groupAlerts = [...this.groupAlerts];
}
}
private checkAlertMatchesFilter(alert: ExtendedGroupAlert): boolean {
if (this.filterStatus && alert.status !== this.filterStatus) {
return false;
}
if (this.filterContent) {
const searchContent = this.filterContent.toLowerCase();
const hasMatchingContent = alert.alerts?.some(singleAlert => singleAlert.content?.toLowerCase().includes(searchContent));
const hasMatchingLabels = Object.entries(alert.groupLabels || {}).some(
([key, value]) => key.toLowerCase().includes(searchContent) || value.toLowerCase().includes(searchContent)
);
if (!hasMatchingContent && !hasMatchingLabels) {
return false;
}
}
return true;
sync() {
this.loadAlertsTable();
}
loadAlertsTable() {
@@ -1,23 +1,20 @@
@import "~src/styles/theme";
.alert-integration-container {
display: flex;
height: 100%;
background: @common-background-color;
background: #fff;
border-radius: 4px;
.data-sources {
width: 240px;
border-right: 1px solid #f0f0f0;
padding: 16px;
background: @common-background-color;
h2 {
margin-bottom: 16px;
font-size: 16px;
font-weight: 500;
}
.source-list {
.source-item {
display: flex;
@@ -26,30 +23,33 @@
cursor: pointer;
border-radius: 4px;
transition: all 0.3s;
&:hover {
background: rgba(0, 0, 0, 0.05);
background: #f5f5f5;
}
&.active {
background: rgba(0, 0, 0, 0.1);
background: #e6f7ff;
}
img {
width: 24px;
height: 24px;
margin-right: 8px;
}
span {
color: #333;
}
}
}
}
.doc-content {
flex: 1;
padding: 24px;
overflow-y: auto;
background: @common-background-color;
h2 {
margin-bottom: 24px;
font-size: 20px;
@@ -57,28 +57,3 @@
}
}
}
[data-theme='dark'] {
:host {
.alert-integration-container {
background: @common-background-color-dark;
.data-sources {
background: @common-background-color-dark;
}
.doc-content {
background: @common-background-color-dark;
}
.source-list {
.source-item {
&:hover {
background: rgba(255, 255, 255, 0.4);
}
&.active {
background: rgba(255, 255, 255, 0.6);
}
}
}
}
}
}
@@ -28,30 +28,8 @@
{{ 'alert.notice.receiver.new' | i18n }}
</button>
</ng-template>
<ng-template #right>
<app-multi-func-input
groupStyle="width: 250px;"
[placeholder]="'alert.notice.receiver.people.name' | i18n"
[(value)]="name"
(valueChange)="onSearch()"
/>
</ng-template>
</app-toolbar>
<nz-table
#fixedTable
[nzPageIndex]="pageIndex"
[nzPageSize]="pageSize"
[nzTotal]="total"
nzFrontPagination="false"
nzShowSizeChanger
[nzShowTotal]="rangeTemplate"
[nzPageSizeOptions]="[8, 15, 25]"
(nzQueryParams)="onTablePageChange($event)"
nzShowPagination="true"
[nzData]="receivers"
[nzLoading]="receiverTableLoading"
[nzScroll]="{ x: '1240px' }"
>
<nz-table #fixedTable [nzData]="receivers" [nzLoading]="receiverTableLoading" [nzScroll]="{ x: '1240px' }" nzFrontPagination="false">
<thead>
<tr>
<th nzAlign="center" nzWidth="15%">{{ 'alert.notice.receiver.people' | i18n }}</th>
@@ -166,9 +144,6 @@
</tr>
</tbody>
</nz-table>
<ng-template #rangeTemplate> {{ 'common.total' | i18n }} {{ total }} </ng-template>
<!-- new or update notice medium pop-up box -->
<nz-modal
(nzOnCancel)="onManageReceiverModalCancel()"
@@ -23,7 +23,6 @@ import { I18NService } from '@core';
import { ALAIN_I18N_TOKEN } from '@delon/theme';
import { NzModalService } from 'ng-zorro-antd/modal';
import { NzNotificationService } from 'ng-zorro-antd/notification';
import { NzTableQueryParams } from 'ng-zorro-antd/table';
import { finalize } from 'rxjs/operators';
import { NoticeReceiver } from '../../../../pojo/NoticeReceiver';
@@ -42,10 +41,6 @@ export class AlertNoticeReceiverComponent implements OnInit {
isManageReceiverModalOkLoading: boolean = false;
isSendTestButtonLoading: boolean = false;
receiver!: NoticeReceiver;
name!: string;
pageIndex: number = 1;
pageSize: number = 8;
total: number = 0;
@ViewChild('receiverForm', { static: false }) receiverForm: NgForm | undefined;
constructor(
@@ -65,14 +60,11 @@ export class AlertNoticeReceiverComponent implements OnInit {
loadReceiversTable() {
this.receiverTableLoading = true;
let receiverInit$ = this.noticeReceiverSvc.getReceivers(this.name, this.pageIndex - 1, this.pageSize).subscribe(
let receiverInit$ = this.noticeReceiverSvc.getReceivers().subscribe(
message => {
this.receiverTableLoading = false;
if (message.code === 0) {
let page = message.data;
this.receivers = page.content;
this.total = page.totalElements;
this.pageIndex = page.number + 1;
this.receivers = message.data;
} else {
console.warn(message.msg);
}
@@ -110,7 +102,6 @@ export class AlertNoticeReceiverComponent implements OnInit {
message => {
if (message.code === 0) {
this.notifySvc.success(this.i18nSvc.fanyi('common.notify.delete-success'), '');
this.updatePageIndex(1);
this.loadReceiversTable();
} else {
this.notifySvc.error(this.i18nSvc.fanyi('common.notify.delete-fail'), message.msg);
@@ -122,11 +113,6 @@ export class AlertNoticeReceiverComponent implements OnInit {
);
}
updatePageIndex(delSize: number) {
const lastPage = Math.max(1, Math.ceil((this.total - delSize) / this.pageSize));
this.pageIndex = this.pageIndex > lastPage ? lastPage : this.pageIndex;
}
onSplitTokenStr(type: number) {
let index = -1;
switch (this.receiver?.type) {
@@ -279,16 +265,4 @@ export class AlertNoticeReceiverComponent implements OnInit {
);
}
}
onTablePageChange(params: NzTableQueryParams) {
const { pageSize, pageIndex } = params;
this.pageIndex = pageIndex;
this.pageSize = pageSize;
this.loadReceiversTable();
}
onSearch() {
this.pageIndex = 1;
this.loadReceiversTable();
}
}
@@ -28,30 +28,8 @@
{{ 'alert.notice.rule.new' | i18n }}
</button>
</ng-template>
<ng-template #right>
<app-multi-func-input
groupStyle="width: 250px;"
[placeholder]="'alert.notice.rule.name' | i18n"
[(value)]="name"
(valueChange)="onSearch()"
/>
</ng-template>
</app-toolbar>
<nz-table
#ruleFixedTable
[nzPageIndex]="pageIndex"
[nzPageSize]="pageSize"
[nzTotal]="total"
nzFrontPagination="false"
nzShowSizeChanger
[nzShowTotal]="rangeTemplate"
[nzPageSizeOptions]="[8, 15, 25]"
(nzQueryParams)="onTablePageChange($event)"
nzShowPagination="true"
[nzData]="rules"
[nzLoading]="ruleTableLoading"
[nzScroll]="{ x: '1240px' }"
>
<nz-table #ruleFixedTable [nzData]="rules" [nzLoading]="ruleTableLoading" [nzScroll]="{ x: '1240px' }" nzFrontPagination="false">
<thead>
<tr>
<th nzAlign="center" nzWidth="15%">{{ 'alert.notice.rule.name' | i18n }}</th>
@@ -110,8 +88,6 @@
</tbody>
</nz-table>
<ng-template #rangeTemplate> {{ 'common.total' | i18n }} {{ total }} </ng-template>
<!-- new or update notice strategy pop-up box -->
<nz-modal
(nzOnCancel)="onManageRuleModalCancel()"
@@ -23,7 +23,6 @@ import { I18NService } from '@core';
import { ALAIN_I18N_TOKEN } from '@delon/theme';
import { NzModalService } from 'ng-zorro-antd/modal';
import { NzNotificationService } from 'ng-zorro-antd/notification';
import { NzTableQueryParams } from 'ng-zorro-antd/table';
import { finalize } from 'rxjs/operators';
import { NoticeReceiver } from '../../../../pojo/NoticeReceiver';
@@ -48,10 +47,6 @@ export class AlertNoticeRuleComponent implements OnInit {
switchReceiver!: NoticeReceiver;
receiversOption: any[] = [];
isLimit: boolean = false;
name!: string;
pageIndex: number = 1;
pageSize: number = 8;
total: number = 0;
@ViewChild('ruleForm', { static: false }) ruleForm: NgForm | undefined;
dayCheckOptions = [
@@ -83,14 +78,11 @@ export class AlertNoticeRuleComponent implements OnInit {
loadRulesTable() {
this.ruleTableLoading = true;
let rulesInit$ = this.noticeRuleSvc.getNoticeRules(this.name, this.pageIndex - 1, this.pageSize).subscribe(
let rulesInit$ = this.noticeRuleSvc.getNoticeRules().subscribe(
message => {
this.ruleTableLoading = false;
if (message.code === 0) {
let page = message.data;
this.rules = page.content;
this.total = page.totalElements;
this.pageIndex = page.number + 1;
this.rules = message.data;
} else {
console.warn(message.msg);
}
@@ -130,7 +122,6 @@ export class AlertNoticeRuleComponent implements OnInit {
message => {
if (message.code === 0) {
this.notifySvc.success(this.i18nSvc.fanyi('common.notify.delete-success'), '');
this.updatePageIndex(1);
this.loadRulesTable();
} else {
this.notifySvc.error(this.i18nSvc.fanyi('common.notify.delete-fail'), message.msg);
@@ -142,11 +133,6 @@ export class AlertNoticeRuleComponent implements OnInit {
);
}
updatePageIndex(delSize: number) {
const lastPage = Math.max(1, Math.ceil((this.total - delSize) / this.pageSize));
this.pageIndex = this.pageIndex > lastPage ? lastPage : this.pageIndex;
}
onNewNoticeRule() {
this.rule = new NoticeRule();
this.rule.templateId = -1;
@@ -227,7 +213,7 @@ export class AlertNoticeRuleComponent implements OnInit {
}
loadReceiversOption() {
let receiverOption$ = this.noticeReceiverSvc.getAllReceivers().subscribe(
let receiverOption$ = this.noticeReceiverSvc.getReceivers().subscribe(
message => {
if (message.code === 0) {
let data = message.data;
@@ -296,7 +282,7 @@ export class AlertNoticeRuleComponent implements OnInit {
}
loadTemplatesOption() {
let templateOption$ = this.noticeTemplateSvc.getAllNoticeTemplates().subscribe(
let templateOption$ = this.noticeTemplateSvc.getNoticeTemplates().subscribe(
message => {
if (message.code === 0) {
let data = message.data;
@@ -440,16 +426,4 @@ export class AlertNoticeRuleComponent implements OnInit {
);
}
}
onTablePageChange(params: NzTableQueryParams) {
const { pageSize, pageIndex } = params;
this.pageIndex = pageIndex;
this.pageSize = pageSize;
this.loadRulesTable();
}
onSearch() {
this.pageIndex = 1;
this.loadRulesTable();
}
}
@@ -27,29 +27,9 @@
{{ 'alert.notice.template.new' | i18n }}
</button>
</ng-template>
<ng-template #right>
<nz-select class="mobile-hide" [nzPlaceHolder]="'monitor.status' | i18n" [(ngModel)]="preset" (ngModelChange)="onPresetStatusChanged()">
<nz-option [nzLabel]="'alert.notice.template.preset.true' | i18n" [nzValue]="true"></nz-option>
<nz-option [nzLabel]="'alert.notice.template.preset.false' | i18n" [nzValue]="false"></nz-option>
</nz-select>
<app-multi-func-input
groupStyle="width: 250px;"
[placeholder]="'alert.notice.template.name' | i18n"
[(value)]="name"
(valueChange)="onSearch()"
/>
</ng-template>
</app-toolbar>
<nz-table
#templateFixedTable
[nzPageIndex]="pageIndex"
[nzPageSize]="pageSize"
[nzTotal]="total"
nzShowSizeChanger
[nzShowTotal]="rangeTemplate"
[nzPageSizeOptions]="[8, 15, 25]"
(nzQueryParams)="onTablePageChange($event)"
nzShowPagination="true"
[nzData]="templates"
[nzLoading]="templateTableLoading"
[nzScroll]="{ x: '1240px' }"
@@ -162,9 +142,6 @@
</tr>
</tbody>
</nz-table>
<ng-template #rangeTemplate> {{ 'common.total' | i18n }} {{ total }} </ng-template>
<!-- new or update notice template pop-up box -->
<nz-modal
(nzOnCancel)="onManageTemplateModalCancel()"
@@ -23,7 +23,6 @@ import { I18NService } from '@core';
import { ALAIN_I18N_TOKEN } from '@delon/theme';
import { NzModalService } from 'ng-zorro-antd/modal';
import { NzNotificationService } from 'ng-zorro-antd/notification';
import { NzTableQueryParams } from 'ng-zorro-antd/table';
import { finalize } from 'rxjs/operators';
import { NoticeRule } from '../../../../pojo/NoticeRule';
@@ -44,11 +43,6 @@ export class AlertNoticeTemplateComponent implements OnInit {
isShowTemplateModalVisible: boolean = false;
template: NoticeTemplate = new NoticeTemplate();
rule: NoticeRule = new NoticeRule();
name!: string;
pageIndex: number = 1;
pageSize: number = 8;
total: number = 0;
preset: boolean = true;
@ViewChild('templateForm', { static: false }) templateForm: NgForm | undefined;
constructor(
@@ -68,14 +62,12 @@ export class AlertNoticeTemplateComponent implements OnInit {
loadTemplatesTable() {
this.templateTableLoading = true;
let templatesInit$ = this.noticeTemplateSvc.getNoticeTemplates(this.name, this.preset, this.pageIndex - 1, this.pageSize).subscribe(
let templatesInit$ = this.noticeTemplateSvc.getNoticeTemplates().subscribe(
message => {
this.templateTableLoading = false;
if (message.code === 0) {
let page = message.data;
this.templates = page.content;
this.total = page.totalElements;
this.pageIndex = page.number + 1;
this.templates = message.data;
// this.templates=this.templates.concat(this.defaultTemplates);
} else {
console.warn(message.msg);
}
@@ -113,7 +105,6 @@ export class AlertNoticeTemplateComponent implements OnInit {
message => {
if (message.code === 0) {
this.notifySvc.success(this.i18nSvc.fanyi('common.notify.delete-success'), '');
this.updatePageIndex(1);
this.loadTemplatesTable();
} else {
this.notifySvc.error(this.i18nSvc.fanyi('common.notify.delete-fail'), message.msg);
@@ -125,11 +116,6 @@ export class AlertNoticeTemplateComponent implements OnInit {
);
}
updatePageIndex(delSize: number) {
const lastPage = Math.max(1, Math.ceil((this.total - delSize) / this.pageSize));
this.pageIndex = this.pageIndex > lastPage ? lastPage : this.pageIndex;
}
onNewNoticeTemplate() {
this.template = new NoticeTemplate();
this.isManageTemplateModalVisible = true;
@@ -223,21 +209,4 @@ export class AlertNoticeTemplateComponent implements OnInit {
);
}
}
onTablePageChange(params: NzTableQueryParams) {
const { pageSize, pageIndex } = params;
this.pageIndex = pageIndex;
this.pageSize = pageSize;
this.loadTemplatesTable();
}
onPresetStatusChanged() {
this.pageIndex = 1;
this.loadTemplatesTable();
}
onSearch() {
this.pageIndex = 1;
this.loadTemplatesTable();
}
}
@@ -70,7 +70,6 @@
[placeholder]="'alert.setting.search' | i18n"
[(value)]="search"
(keydown.enter)="onFilterChange()"
(cleared)="onFilterChange()"
/>
</ng-template>
</app-toolbar>
@@ -418,13 +417,13 @@
</nz-form-control>
</nz-form-item>
<nz-form-item *ngIf="define.type == 'periodic'" [ngStyle]="{ marginBottom: '5px' }">
<nz-form-label [nzSpan]="7" nzFor="datasource" nzRequired="true" [nzTooltipTitle]="'alert.setting.rule.label' | i18n">
<nz-form-label [nzSpan]="7" nzFor="promql" nzRequired="true" [nzTooltipTitle]="'alert.setting.rule.label' | i18n">
{{ 'alert.setting.rule' | i18n }}
</nz-form-label>
<nz-form-control [nzSpan]="12" [nzErrorTip]="'validation.required' | i18n">
<ng-container *ngIf="cascadeValues[1] !== 'availability'">
<nz-radio-group [(ngModel)]="define.datasource" nzButtonStyle="solid" name="datasource" id="datasource">
<label nz-radio-button [nzValue]="'promql'">
<nz-radio-group [(ngModel)]="define.datasource" nzButtonStyle="solid" id="promql">
<label nz-radio-button [nzValue]="'periodic'">
{{ 'PromQL' | i18n }}
</label>
</nz-radio-group>
@@ -477,7 +476,6 @@
[nzPlaceHolder]="'alert.notice.rule.priority.placeholder' | i18n"
name="severity"
id="severity"
required
>
<nz-option [nzValue]="'emergency'" [nzLabel]="'alert.severity.0' | i18n"></nz-option>
<nz-option [nzValue]="'critical'" [nzLabel]="'alert.severity.1' | i18n"></nz-option>
@@ -500,7 +498,7 @@
</nz-form-label>
<nz-form-control [nzSpan]="12" [nzErrorTip]="'validation.required' | i18n">
<div class="template-input-wrapper">
<div *ngIf="define.type === 'realtime'" class="draggable-vars-container">
<div class="draggable-vars-container">
<div *ngFor="let env of templateEnvVars" class="draggable-tag" draggable="true" (dragstart)="onDragStart($event, env)">
<div class="key-value-block">
<span class="var-key">{{ env.name }}</span>
@@ -200,7 +200,7 @@ export class AlertSettingComponent implements OnInit {
this.tableLoading = true;
const translationSearchList: string[] = [];
let trimSearch = '';
if (this.search && this.search.trim() !== '') {
if (this.search !== undefined && this.search.trim() !== '') {
trimSearch = this.search.trim();
}
// Filter entries based on search input
@@ -240,7 +240,6 @@ export class AlertSettingComponent implements OnInit {
this.isSelectTypeModalVisible = false;
this.define = new AlertDefine();
this.define.type = type;
this.severity = '';
this.userExpr = '';
this.selectedMonitorIds = new Set<number>();
// Set default period for periodic alert
@@ -306,7 +306,7 @@
[delay]="300"
[zoomOnHover]="{ scale: 1.4, transitionTime: 0.6, delay: 0.4 }"
[overflow]="false"
[background]="theme == 'dark' ? '#141414' : 'white'"
[background]="'white no-repeat fixed center'"
>
</angular-tag-cloud>
</nz-spin>
@@ -1,3 +1,4 @@
@import '@delon/theme/index';
:host ::ng-deep {
.ant-timeline {
.ant-timeline-label {
@@ -34,7 +34,6 @@ import { AlertService } from '../../service/alert.service';
import { CollectorService } from '../../service/collector.service';
import { MonitorService } from '../../service/monitor.service';
import { TagService } from '../../service/tag.service';
import { ThemeService } from '../../service/theme.service';
import { formatTagName } from '../../shared/utils/common-util';
@Component({
@@ -52,11 +51,9 @@ export class DashboardComponent implements OnInit, OnDestroy {
private collectorSvc: CollectorService,
@Inject(ALAIN_I18N_TOKEN) private i18nSvc: I18NService,
private router: Router,
private themeSvc: ThemeService,
private cdr: ChangeDetectorRef
) {}
theme: string = 'default';
// Tag Word Cloud
wordCloudData: CloudData[] = [];
wordCloudDataLoading: boolean = false;
@@ -181,7 +178,6 @@ export class DashboardComponent implements OnInit, OnDestroy {
alertContentLoading: boolean = false;
ngOnInit(): void {
this.theme = this.themeSvc.getTheme() || 'default';
this.appsCountTheme = {
title: {
text: `{a|${this.i18nSvc.fanyi('dashboard.monitors.title')}}`,
@@ -113,7 +113,7 @@
<nz-table
*ngIf="!monitor && isTable"
nzSize="small"
[nzNoResult]="'monitor.detail.chart.no-data' | i18n"
nzNoResult="No Metrics Data"
[nzFrontPagination]="false"
[nzShowPagination]="false"
[nzData]="valueRows"
@@ -143,7 +143,7 @@
<nz-table
*ngIf="!monitor && !isTable"
nzSize="small"
[nzNoResult]="'monitor.detail.chart.no-data' | i18n"
nzNoResult="No Metrics Data"
[nzFrontPagination]="false"
[nzShowPagination]="false"
[nzData]="valueRows"
@@ -150,32 +150,17 @@ export class MonitorFormComponent implements OnChanges {
}
onParamBooleanChanged(booleanValue: boolean, field: string) {
if (this.monitor.app === 'api') {
if (field === 'ssl') {
const portParam = this.params.find(param => param.field === 'port');
if (portParam) {
if (booleanValue && (portParam.paramValue == null || parseInt(portParam.paramValue) === 80)) {
portParam.paramValue = 443;
this.notifySvc.info(this.i18nSvc.fanyi('common.notice'), this.i18nSvc.fanyi('monitor.new.notify.change-to-https'));
}
if (!booleanValue && (portParam.paramValue == null || parseInt(portParam.paramValue) === 443)) {
portParam.paramValue = 80;
this.notifySvc.info(this.i18nSvc.fanyi('common.notice'), this.i18nSvc.fanyi('monitor.new.notify.change-to-http'));
}
// For SSL port linkage, port 80 by default is not enabled, but port 443 by default is enabled
if (field === 'ssl') {
const portParam = this.params.find(param => param.field === 'port');
if (portParam) {
if (booleanValue && (portParam.paramValue == null || parseInt(portParam.paramValue) === 80)) {
portParam.paramValue = 443;
this.notifySvc.info(this.i18nSvc.fanyi('common.notice'), this.i18nSvc.fanyi('monitor.new.notify.change-to-https'));
}
}
} else if (this.monitor.app === 'ftp') {
if (field === 'ssl') {
const portParam = this.params.find(param => param.field === 'port');
if (portParam) {
if (booleanValue && (portParam.paramValue == null || parseInt(portParam.paramValue) === 21)) {
portParam.paramValue = 22;
this.notifySvc.info(this.i18nSvc.fanyi('common.notice'), this.i18nSvc.fanyi('monitor.new.notify.change-to-sftp'));
}
if (!booleanValue && (portParam.paramValue == null || parseInt(portParam.paramValue) === 22)) {
portParam.paramValue = 21;
this.notifySvc.info(this.i18nSvc.fanyi('common.notice'), this.i18nSvc.fanyi('monitor.new.notify.change-to-ftp'));
}
if (!booleanValue && (portParam.paramValue == null || parseInt(portParam.paramValue) === 443)) {
portParam.paramValue = 80;
this.notifySvc.info(this.i18nSvc.fanyi('common.notice'), this.i18nSvc.fanyi('monitor.new.notify.change-to-http'));
}
}
}
@@ -120,7 +120,7 @@
<app-multi-func-input
groupStyle="width: 120px;"
class="mobile-hide"
[placeholder]="'monitor.search.label' | i18n"
[placeholder]="'monitor.search.tag' | i18n"
[(value)]="labels"
(valueChange)="onTagChanged()"
/>
@@ -17,7 +17,7 @@
~ under the License.
-->
<div class="br-8" style="background-color: rgb(198 189 189 / 39%); padding: 20px; box-shadow: 7px 5px #b421cc">
<div class="br-8" style="background-color: snow; padding: 20px; box-shadow: 7px 5px #b421cc">
<form nz-form [formGroup]="form" (ngSubmit)="submit()" role="form">
<nz-tabset [nzAnimated]="false" class="tabs" (nzSelectChange)="switch($event)">
<nz-tab [nzTitle]="'app.login.tab-login-credentials' | i18n">
@@ -27,7 +27,7 @@
<nz-divider></nz-divider>
<nz-layout style="height: 100vh; overflow: hidden">
<nz-sider [nzTheme]="theme == 'dark' ? 'dark' : 'light'" style="height: 100%; overflow: hidden" [nzTrigger]="null">
<nz-sider nzTheme="light" style="height: 100%; overflow: hidden" [nzTrigger]="null">
<app-monitor-select-menu
[loading]="menuLoading"
[data]="appMenusArr"
@@ -107,7 +107,7 @@
[nzOriginalText]="originalCode"
[(ngModel)]="code"
[nzEditorMode]="'diff'"
[nzEditorOption]="{ language: 'yaml', theme: 'vs', folding: true, automaticLayout: true }"
[nzEditorOption]="{ language: 'yaml', theme: 'vs-dark', folding: true, automaticLayout: true }"
></nz-code-editor>
</div>
</nz-content>
@@ -28,7 +28,6 @@ import { finalize } from 'rxjs/operators';
import { AppDefineService } from '../../../service/app-define.service';
import { GeneralConfigService } from '../../../service/general-config.service';
import { ThemeService } from '../../../service/theme.service';
@Component({
selector: 'app-define',
@@ -45,7 +44,6 @@ export class DefineComponent implements OnInit {
private startUpSvc: StartupService,
private route: ActivatedRoute,
private router: Router,
private themeSvc: ThemeService,
@Inject(ALAIN_I18N_TOKEN) private i18nSvc: I18NService
) {}
@@ -56,8 +54,7 @@ export class DefineComponent implements OnInit {
loading = false;
code: string = '';
originalCode: string = '';
dark: boolean = false;
theme: string = 'default';
dark: boolean = true;
currentApp: any = null;
saveLoading = false;
deleteLoading = false;
@@ -69,7 +66,6 @@ export class DefineComponent implements OnInit {
this.loadAppDefineContent(this.currentApp);
}
});
this.theme = this.themeSvc.getTheme() || 'default';
this.loadMenus();
this.code = `${this.i18nSvc.fanyi('define.new.code')}\n\n\n\n\n`;
this.originalCode = this.i18nSvc.fanyi('define.new.code');
@@ -31,7 +31,6 @@
<nz-option [nzValue]="'en_US'" [nzLabel]="'settings.system-config.locale.en_US' | i18n"></nz-option>
<nz-option [nzValue]="'zh_CN'" [nzLabel]="'settings.system-config.locale.zh_CN' | i18n"></nz-option>
<nz-option [nzValue]="'zh_TW'" [nzLabel]="'settings.system-config.locale.zh_TW' | i18n"></nz-option>
<nz-option [nzValue]="'ja_JP'" [nzLabel]="'settings.system-config.locale.ja-JP' | i18n"></nz-option>
</nz-select>
</se>
<se [label]="'settings.system-config.timezone' | i18n" [error]="'validation.required' | i18n">
@@ -58,7 +58,7 @@ export class SystemConfigComponent implements OnInit {
if (message.code === 0) {
if (message.data) {
this.config = message.data;
this.config.theme = this.themeService.getTheme() || 'default';
this.changeTheme(this.config.theme); // update theme after config is loaded
} else {
this.config = new SystemConfig();
}
@@ -94,7 +94,6 @@ export class SystemConfigComponent implements OnInit {
this.i18nSvc.loadLangData(language).subscribe(res => {
this.i18nSvc.use(language, res);
this.settings.setLayout('lang', language);
this.themeService.setTheme(this.config.theme);
setTimeout(() => this.doc.location.reload());
});
} else {
@@ -106,4 +105,8 @@ export class SystemConfigComponent implements OnInit {
}
);
}
changeTheme(theme: string): void {
this.themeService.changeTheme(theme);
}
}
@@ -1,11 +1,10 @@
@import "~src/styles/theme";
.tag-cards-container {
padding: 24px 0;
.tag-card {
transition: all 0.3s;
border-radius: 8px;
background: #fff;
&:hover {
transform: translateY(-2px);
@@ -24,7 +23,8 @@
.ant-card-actions {
border-radius: 0 0 8px 8px;
border-top: 1px solid rgba(240, 240, 240, 0.5);
background: #fafafa;
border-top: 1px solid #f0f0f0;
min-height: 32px;
> li {
@@ -34,7 +34,7 @@
padding: 4px 0;
&:hover {
color: @primary-color;
color: #1890ff;
}
i {
@@ -51,8 +51,20 @@
align-items: center;
justify-content: center;
margin-bottom: 8px;
.tag-name {
font-size: 18px;
padding: 4px 8px;
border-radius: 4px;
margin: 0;
font-weight: 500;
line-height: 1.4;
text-align: center;
}
}
.tag-description {
color: rgba(35, 34, 34, 0.65);
font-size: 12px;
margin: 0;
display: -webkit-box;
@@ -65,27 +77,31 @@
}
}
.tag-card {
:global {
.ant-card-body {
padding: 12px;
}
}
}
.tag-content {
min-height: 80px;
display: flex;
flex-direction: column;
}
.tag-name {
font-size: 18px;
padding: 4px 8px;
border-radius: 4px;
margin: 0;
font-weight: 500;
line-height: 1.4;
text-align: center;
background: @common-background-color;
.tag-header {
margin-bottom: 8px;
}
[data-theme='dark'] {
:host {
.tag-name {
background: @common-background-color-dark;
}
}
.tag-description {
font-size: 12px;
color: rgba(0, 0, 0, 0.65);
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
line-height: 1.5;
}
@@ -57,8 +57,7 @@ export class LocalStorageService {
return localStorage.getItem(AuthorizationConst) != null;
}
public clearAuthorization() {
localStorage.removeItem(AuthorizationConst);
localStorage.removeItem(RefreshTokenConst);
public clear() {
localStorage.clear();
}
}
@@ -17,17 +17,15 @@
* under the License.
*/
import { HttpClient, HttpParams } from '@angular/common/http';
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { Message } from '../pojo/Message';
import { NoticeReceiver } from '../pojo/NoticeReceiver';
import { Page } from '../pojo/Page';
const notice_receiver_uri = '/notice/receiver';
const notice_receivers_uri = '/notice/receivers';
const notice_receivers_all_uri = '/notice/receivers/all';
const notice_receiver_send_test_msg_uri = '/notice/receiver/send-test-msg';
@Injectable({
@@ -48,21 +46,8 @@ export class NoticeReceiverService {
return this.http.delete<Message<any>>(`${notice_receiver_uri}/${receiverId}`);
}
public getReceivers(name: string, pageIndex: number, pageSize: number): Observable<Message<Page<NoticeReceiver>>> {
pageIndex = pageIndex ? pageIndex : 0;
pageSize = pageSize ? pageSize : 8;
let httpParams = new HttpParams();
httpParams = httpParams.append('pageIndex', pageIndex);
httpParams = httpParams.append('pageSize', pageSize);
if (name != undefined && name != null && name != '') {
httpParams = httpParams.append('name', name);
}
const options = { params: httpParams };
return this.http.get<Message<Page<NoticeReceiver>>>(notice_receivers_uri, options);
}
public getAllReceivers(): Observable<Message<NoticeReceiver[]>> {
return this.http.get<Message<NoticeReceiver[]>>(notice_receivers_all_uri);
public getReceivers(): Observable<Message<NoticeReceiver[]>> {
return this.http.get<Message<NoticeReceiver[]>>(notice_receivers_uri);
}
public getReceiver(receiverId: number): Observable<Message<NoticeReceiver>> {
+3 -13
View File
@@ -17,13 +17,12 @@
* under the License.
*/
import { HttpClient, HttpParams } from '@angular/common/http';
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { Message } from '../pojo/Message';
import { NoticeRule } from '../pojo/NoticeRule';
import { Page } from '../pojo/Page';
const notice_rule_uri = '/notice/rule';
const notice_rules_uri = '/notice/rules';
@@ -46,17 +45,8 @@ export class NoticeRuleService {
return this.http.delete<Message<any>>(`${notice_rule_uri}/${ruleId}`);
}
public getNoticeRules(name: string, pageIndex: number, pageSize: number): Observable<Message<Page<NoticeRule>>> {
pageIndex = pageIndex ? pageIndex : 0;
pageSize = pageSize ? pageSize : 8;
let httpParams = new HttpParams();
httpParams = httpParams.append('pageIndex', pageIndex);
httpParams = httpParams.append('pageSize', pageSize);
if (name != undefined && name != null && name != '') {
httpParams = httpParams.append('name', name);
}
const options = { params: httpParams };
return this.http.get<Message<Page<NoticeRule>>>(notice_rules_uri, options);
public getNoticeRules(): Observable<Message<NoticeRule[]>> {
return this.http.get<Message<NoticeRule[]>>(notice_rules_uri);
}
public getNoticeRuleById(ruleId: number): Observable<Message<NoticeRule>> {
@@ -17,17 +17,15 @@
* under the License.
*/
import { HttpClient, HttpParams } from '@angular/common/http';
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { Message } from '../pojo/Message';
import { NoticeTemplate } from '../pojo/NoticeTemplate';
import { Page } from '../pojo/Page';
const notice_template_uri = '/notice/template';
const notice_templates_uri = '/notice/templates';
const notice_templates_all_uri = '/notice/templates/all';
const default_notice_templates_uri = '/notice/default_templates';
@Injectable({
providedIn: 'root'
@@ -47,22 +45,8 @@ export class NoticeTemplateService {
return this.http.delete<Message<any>>(`${notice_template_uri}/${templateId}`);
}
public getNoticeTemplates(name: string, preset: boolean, pageIndex: number, pageSize: number): Observable<Message<Page<NoticeTemplate>>> {
pageIndex = pageIndex ? pageIndex : 0;
pageSize = pageSize ? pageSize : 8;
let httpParams = new HttpParams();
httpParams = httpParams.append('pageIndex', pageIndex);
httpParams = httpParams.append('pageSize', pageSize);
httpParams = httpParams.append('preset', preset);
if (name != undefined && name != null && name != '') {
httpParams = httpParams.append('name', name);
}
const options = { params: httpParams };
return this.http.get<Message<Page<NoticeTemplate>>>(notice_templates_uri, options);
}
public getAllNoticeTemplates(): Observable<Message<NoticeTemplate[]>> {
return this.http.get<Message<NoticeTemplate[]>>(notice_templates_all_uri);
public getNoticeTemplates(): Observable<Message<NoticeTemplate[]>> {
return this.http.get<Message<NoticeTemplate[]>>(notice_templates_uri);
}
public getDefaultNoticeTemplates(): Observable<Message<NoticeTemplate[]>> {
+8 -5
View File
@@ -36,10 +36,11 @@ export class ThemeService {
return localStorage.getItem(this.themeKey);
}
changeTheme(theme: string | null): void {
if (theme == null) {
theme = this.getTheme();
}
clearTheme(): void {
localStorage.removeItem(this.themeKey);
}
changeTheme(theme: string): void {
const style = this.doc.createElement('link');
style.type = 'text/css';
style.rel = 'stylesheet';
@@ -56,6 +57,9 @@ export class ThemeService {
const compactDom = this.doc.getElementById('compact-theme');
if (compactDom) compactDom.remove();
this.clearTheme();
return;
}
@@ -69,6 +73,5 @@ export class ThemeService {
// add new theme
this.doc.body.appendChild(style);
this.doc.body.setAttribute('data-theme', theme);
}
}

Some files were not shown because too many files have changed in this diff Show More