mirror of
https://github.com/dromara/hertzbeat.git
synced 2026-09-17 18:19:02 +00:00
Compare commits
21
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
047d59381c | ||
|
|
cdb4219065 | ||
|
|
5e69f4dfd0 | ||
|
|
aeb0083394 | ||
|
|
cabe8d8ff9 | ||
|
|
02f635bdd3 | ||
|
|
4ac8170d21 | ||
|
|
5641a5ca89 | ||
|
|
de8bd79ef9 | ||
|
|
30332969ee | ||
|
|
7dbcaaeda8 | ||
|
|
4ddf0a9f3f | ||
|
|
f1b1e3f7d6 | ||
|
|
921b8e5713 | ||
|
|
4009525f81 | ||
|
|
e0c7b4e111 | ||
|
|
5e01482caa | ||
|
|
028e2fbcbe | ||
|
|
d00cc6dcf4 | ||
|
|
4b7fdd6985 | ||
|
|
e45ec431aa |
@@ -73,7 +73,7 @@ jobs:
|
||||
|
||||
# upload application logs
|
||||
- name: Upload logs & API test reports
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: hz-logs-${{ github.run_id }}
|
||||
|
||||
@@ -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@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: docs-cn-pdf
|
||||
path: docs-cn.pdf
|
||||
@@ -47,7 +47,7 @@ jobs:
|
||||
retention-days: 1
|
||||
|
||||
- name: Upload results
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: docs-en-pdf
|
||||
path: docs-en.pdf
|
||||
|
||||
@@ -40,6 +40,11 @@
|
||||
<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>
|
||||
|
||||
+132
-50
@@ -17,90 +17,172 @@
|
||||
|
||||
package org.apache.hertzbeat.alert.calculate;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Objects;
|
||||
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
|
||||
@RequiredArgsConstructor
|
||||
@Component
|
||||
public class PeriodicAlertCalculator {
|
||||
|
||||
private static final String VALUE = "__value__";
|
||||
private static final String TIMESTAMP = "__timestamp__";
|
||||
|
||||
private final DataSourceService dataSourceService;
|
||||
private final JexlExpressionRunner expressionRunner;
|
||||
private final Map<String, SingleAlert> notRecoveredAlertMap = new ConcurrentHashMap<>(16);
|
||||
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;
|
||||
|
||||
public PeriodicAlertCalculator(DataSourceService dataSourceService, AlarmCommonReduce alarmCommonReduce) {
|
||||
this.dataSourceService = dataSourceService;
|
||||
this.alarmCommonReduce = alarmCommonReduce;
|
||||
this.pendingAlertMap = new ConcurrentHashMap<>(8);
|
||||
this.firingAlertMap = new ConcurrentHashMap<>(8);
|
||||
}
|
||||
|
||||
public List<SingleAlert> calculate(AlertDefine rule) {
|
||||
public void calculate(AlertDefine rule) {
|
||||
if (!rule.isEnable() || StringUtils.isEmpty(rule.getExpr())) {
|
||||
return Collections.emptyList();
|
||||
log.error("Periodic rule {} is disabled or expression is empty", rule.getName());
|
||||
return;
|
||||
}
|
||||
// todo: implement the following logic
|
||||
long currentTimeMilli = System.currentTimeMillis();
|
||||
try {
|
||||
// Execute query
|
||||
List<Map<String, Object>> queryResults = dataSourceService.query(
|
||||
rule.getDatasource(),
|
||||
rule.getExpr()
|
||||
);
|
||||
|
||||
if (CollectionUtils.isEmpty(queryResults)) {
|
||||
return Collections.emptyList();
|
||||
// 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 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 boolean execAlertExpression(Map<String, Object> result, String expr) {
|
||||
return false;
|
||||
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 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());
|
||||
private void handleRecoveredAlert(Map<String, String> fingerprints) {
|
||||
String fingerprint = calculateFingerprint(fingerprints);
|
||||
SingleAlert firingAlert = firingAlertMap.remove(fingerprint);
|
||||
if (firingAlert != null) {
|
||||
return Collections.singletonList(buildResolvedAlert(rule, firingAlert));
|
||||
// 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.emptyList();
|
||||
pendingAlertMap.remove(fingerprint);
|
||||
}
|
||||
|
||||
private SingleAlert buildResolvedAlert(AlertDefine rule, SingleAlert firingAlert) {
|
||||
return null;
|
||||
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]));
|
||||
}
|
||||
}
|
||||
|
||||
+70
-4
@@ -17,13 +17,79 @@
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* period alert rule scheduler
|
||||
* Periodic Alert Rule Scheduler
|
||||
*/
|
||||
@Slf4j
|
||||
public class PeriodicAlertRuleScheduler {
|
||||
|
||||
// todo implement the following logic
|
||||
@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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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
-9
@@ -31,6 +31,7 @@ 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;
|
||||
@@ -82,9 +83,18 @@ 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<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)));
|
||||
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()));
|
||||
}
|
||||
|
||||
@GetMapping(path = "/receiver/{id}")
|
||||
@@ -129,9 +139,11 @@ 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<List<NoticeRule>>> getRules(
|
||||
@Parameter(description = "en: Recipient name", example = "rule1") @RequestParam(required = false) final String name) {
|
||||
return ResponseEntity.ok(Message.success(noticeConfigService.getNoticeRules(name)));
|
||||
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)));
|
||||
}
|
||||
|
||||
@GetMapping(path = "/rule/{id}")
|
||||
@@ -176,12 +188,22 @@ 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<List<NoticeTemplate>>> getTemplates(
|
||||
@Parameter(description = "Template name,support fuzzy query", example = "rule1") @RequestParam(required = false) final String name) {
|
||||
List<NoticeTemplate> templatePage = noticeConfigService.getNoticeTemplates(name);
|
||||
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);
|
||||
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")
|
||||
|
||||
+20
-16
@@ -23,11 +23,13 @@ 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;
|
||||
@@ -45,16 +47,18 @@ 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) {
|
||||
List<AlertNotifyHandler> alertNotifyHandlerList, PluginRunner pluginRunner, AlertSseManager emitterManager) {
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -104,27 +108,27 @@ public class AlertNoticeDispatch {
|
||||
public void dispatchAlarm(GroupAlert groupAlert) {
|
||||
if (groupAlert != null) {
|
||||
// Determining alarm type storage
|
||||
alertStoreHandler.store(groupAlert);
|
||||
GroupAlert storedGroupAlert = alertStoreHandler.store(groupAlert);
|
||||
// Notice distribution
|
||||
sendNotify(groupAlert);
|
||||
sendNotify(storedGroupAlert);
|
||||
// Execute the plugin if enable (Compatible with old version plugins, will be removed in later versions)
|
||||
pluginRunner.pluginExecute(Plugin.class, plugin -> plugin.alert(groupAlert));
|
||||
pluginRunner.pluginExecute(Plugin.class, plugin -> plugin.alert(storedGroupAlert));
|
||||
// Execute the plugin if enable with params
|
||||
pluginRunner.pluginExecute(PostAlertPlugin.class, (afterAlertPlugin, pluginContext) -> afterAlertPlugin.execute(groupAlert, pluginContext));
|
||||
pluginRunner.pluginExecute(PostAlertPlugin.class, (afterAlertPlugin, pluginContext) -> afterAlertPlugin.execute(storedGroupAlert, pluginContext));
|
||||
// Send alert to the sse client
|
||||
emitterManager.broadcast(JsonUtil.toJson(storedGroupAlert));
|
||||
}
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
}))));
|
||||
}
|
||||
}
|
||||
|
||||
+3
-1
@@ -28,7 +28,9 @@ 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
|
||||
*/
|
||||
void store(GroupAlert alert);
|
||||
GroupAlert store(GroupAlert alert);
|
||||
}
|
||||
|
||||
+26
-14
@@ -17,6 +17,7 @@
|
||||
|
||||
package org.apache.hertzbeat.alert.notice.impl;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -46,44 +47,53 @@ final class DbAlertStoreHandlerImpl implements AlertStoreHandler {
|
||||
private final SingleAlertDao singleAlertDao;
|
||||
|
||||
@Override
|
||||
public void store(GroupAlert groupAlert) {
|
||||
public GroupAlert store(GroupAlert groupAlert) {
|
||||
if (groupAlert == null || groupAlert.getAlerts() == null || groupAlert.getAlerts().isEmpty()) {
|
||||
log.error("The Group Alerts is empty, ignore store");
|
||||
return;
|
||||
return groupAlert;
|
||||
}
|
||||
// 1. Find existing alert group
|
||||
GroupAlert existGroupAlert = groupAlertDao.findByGroupKey(groupAlert.getGroupKey());
|
||||
|
||||
// 2. Process individual alerts
|
||||
Set<String> alertFingerprints = new HashSet<>(8);
|
||||
groupAlert.getAlerts().forEach(singleAlert -> {
|
||||
|
||||
List<SingleAlert> originalAlerts = groupAlert.getAlerts();
|
||||
List<SingleAlert> newAlerts = new ArrayList<>();
|
||||
|
||||
|
||||
for (SingleAlert singleAlert : originalAlerts) {
|
||||
SingleAlert existAlert = singleAlertDao.findByFingerprint(singleAlert.getFingerprint());
|
||||
|
||||
if (existAlert != null) {
|
||||
// Update existing alert
|
||||
// Update the existing alert with the ID and creation time from the database
|
||||
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())) {
|
||||
// Transition to resolved state
|
||||
// If the alert is resolved, set the end time (if not already set) and copy other fields from the existing alert
|
||||
if (singleAlert.getEndAt() == null) {
|
||||
singleAlert.setEndAt(System.currentTimeMillis());
|
||||
singleAlert.setEndAt(System.currentTimeMillis());
|
||||
}
|
||||
singleAlert.setStartAt(existAlert.getStartAt());
|
||||
singleAlert.setActiveAt(existAlert.getActiveAt());
|
||||
singleAlert.setTriggerTimes(existAlert.getTriggerTimes());
|
||||
}
|
||||
}
|
||||
alertFingerprints.add(singleAlert.getFingerprint());
|
||||
singleAlertDao.save(singleAlert);
|
||||
});
|
||||
|
||||
SingleAlert savedSingleAlert = singleAlertDao.save(singleAlert);
|
||||
newAlerts.add(savedSingleAlert);
|
||||
alertFingerprints.add(savedSingleAlert.getFingerprint());
|
||||
}
|
||||
groupAlert.setAlerts(newAlerts);
|
||||
// 3. Process resolved alerts
|
||||
if (existGroupAlert != null) {
|
||||
List<String> existFingerprints = existGroupAlert.getAlertFingerprints();
|
||||
@@ -120,6 +130,8 @@ final class DbAlertStoreHandlerImpl implements AlertStoreHandler {
|
||||
|
||||
// 4. Save alert group
|
||||
groupAlert.setAlertFingerprints(alertFingerprints.stream().toList());
|
||||
groupAlertDao.save(groupAlert);
|
||||
GroupAlert savedGroupAlert = groupAlertDao.save(groupAlert);
|
||||
savedGroupAlert.setAlerts(groupAlert.getAlerts());
|
||||
return savedGroupAlert;
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -26,10 +26,10 @@ import java.util.Map;
|
||||
public interface DataSourceService {
|
||||
|
||||
/**
|
||||
* execute query
|
||||
* execute query expr calculate
|
||||
* @param datasource datasource
|
||||
* @param query query
|
||||
* @param expr query expr
|
||||
* @return result
|
||||
*/
|
||||
List<Map<String, Object>> query(String datasource, String query);
|
||||
List<Map<String, Object>> calculate(String datasource, String expr);
|
||||
}
|
||||
|
||||
+25
-4
@@ -23,6 +23,7 @@ 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
|
||||
@@ -32,23 +33,32 @@ 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
|
||||
*/
|
||||
List<NoticeReceiver> getNoticeReceivers(String name);
|
||||
Page<NoticeReceiver> getNoticeReceivers(String name, int pageIndex, int pageSize);
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
List<NoticeTemplate> getNoticeTemplates(String name);
|
||||
Page<NoticeTemplate> getNoticeTemplates(String name, boolean preset, int pageIndex, int pageSize);
|
||||
|
||||
/**
|
||||
* Dynamic conditional query
|
||||
* @param name Recipient name
|
||||
* @param name Recipient name ,support fuzzy query
|
||||
* @param pageIndex Page number
|
||||
* @param pageSize Number of records per page
|
||||
* @return Search result
|
||||
*/
|
||||
List<NoticeRule> getNoticeRules(String name);
|
||||
Page<NoticeRule> getNoticeRules(String name, int pageIndex, int pageSize);
|
||||
|
||||
/**
|
||||
* Add a notification recipient
|
||||
@@ -154,4 +164,15 @@ 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();
|
||||
}
|
||||
|
||||
+11
-1
@@ -25,6 +25,7 @@ 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;
|
||||
@@ -67,6 +68,9 @@ public class AlertDefineServiceImpl implements AlertDefineService {
|
||||
|
||||
@Autowired
|
||||
private AlertDefineDao alertDefineDao;
|
||||
|
||||
@Autowired
|
||||
private PeriodicAlertRuleScheduler periodicAlertRuleScheduler;
|
||||
|
||||
private final Map<String, AlertDefineImExportService> alertDefineImExportServiceMap = new HashMap<>();
|
||||
|
||||
@@ -98,19 +102,22 @@ public class AlertDefineServiceImpl implements AlertDefineService {
|
||||
|
||||
@Override
|
||||
public void addAlertDefine(AlertDefine alertDefine) throws RuntimeException {
|
||||
alertDefineDao.save(alertDefine);
|
||||
alertDefine = alertDefineDao.save(alertDefine);
|
||||
periodicAlertRuleScheduler.updateSchedule(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();
|
||||
}
|
||||
|
||||
@@ -123,6 +130,9 @@ 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();
|
||||
}
|
||||
|
||||
|
||||
+303
-9
@@ -17,13 +17,21 @@
|
||||
|
||||
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
|
||||
@@ -33,21 +41,307 @@ import java.util.Map;
|
||||
public class DataSourceServiceImpl implements DataSourceService {
|
||||
|
||||
@Autowired(required = false)
|
||||
private Map<String, QueryExecutor> executors;
|
||||
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__";
|
||||
|
||||
@Override
|
||||
public List<Map<String, Object>> query(String datasource, String query) {
|
||||
QueryExecutor executor = executors.get(datasource);
|
||||
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);
|
||||
if (executor == null) {
|
||||
throw new IllegalArgumentException("Unsupported datasource: " + datasource);
|
||||
}
|
||||
return executor.execute(query);
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public interface QueryExecutor {
|
||||
List<Map<String, Object>> execute(String 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;
|
||||
}
|
||||
}
|
||||
|
||||
+76
-27
@@ -18,18 +18,6 @@
|
||||
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;
|
||||
@@ -51,10 +39,28 @@ 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
|
||||
*/
|
||||
@@ -80,44 +86,87 @@ public class NoticeConfigServiceImpl implements NoticeConfigService, CommandLine
|
||||
private AlertNoticeDispatch dispatcherAlarm;
|
||||
|
||||
@Override
|
||||
public List<NoticeReceiver> getNoticeReceivers(String name) {
|
||||
public Page<NoticeReceiver> getNoticeReceivers(String name, int pageIndex, int pageSize) {
|
||||
Specification<NoticeReceiver> specification = (root, query, criteriaBuilder) -> {
|
||||
Predicate predicate = criteriaBuilder.conjunction();
|
||||
if (StringUtils.isNotBlank(name)) {
|
||||
Predicate predicateName = criteriaBuilder.like(root.get("name"), "%" + name + "%");
|
||||
Predicate predicateName = criteriaBuilder.like(
|
||||
criteriaBuilder.lower(root.get("name")), "%" + name.toLowerCase() + "%"
|
||||
);
|
||||
predicate = criteriaBuilder.and(predicateName);
|
||||
}
|
||||
return predicate;
|
||||
};
|
||||
return noticeReceiverDao.findAll(specification);
|
||||
return noticeReceiverDao.findAll(specification, PageRequest.of(pageIndex, pageSize, Sort.by(Sort.Direction.DESC, "id")));
|
||||
}
|
||||
|
||||
@Override
|
||||
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);
|
||||
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);
|
||||
}
|
||||
return predicate;
|
||||
};
|
||||
|
||||
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() {
|
||||
List<NoticeTemplate> defaultTemplates = new LinkedList<>(PRESET_TEMPLATE.values());
|
||||
defaultTemplates.addAll(noticeTemplateDao.findAll(specification));
|
||||
defaultTemplates.addAll(noticeTemplateDao.findAll());
|
||||
return defaultTemplates;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<NoticeRule> getNoticeRules(String name) {
|
||||
public Page<NoticeRule> getNoticeRules(String name, int pageIndex, int pageSize) {
|
||||
Specification<NoticeRule> specification = (root, query, criteriaBuilder) -> {
|
||||
Predicate predicate = criteriaBuilder.conjunction();
|
||||
if (StringUtils.isNotBlank(name)) {
|
||||
Predicate predicateName = criteriaBuilder.like(root.get("name"), "%" + name + "%");
|
||||
Predicate predicateName = criteriaBuilder.like(
|
||||
criteriaBuilder.lower(root.get("name")), "%" + name.toLowerCase() + "%"
|
||||
);
|
||||
predicate = criteriaBuilder.and(predicateName);
|
||||
}
|
||||
return predicate;
|
||||
};
|
||||
return noticeRuleDao.findAll(specification);
|
||||
return noticeRuleDao.findAll(specification, PageRequest.of(pageIndex, pageSize, Sort.by(Sort.Direction.DESC, "id")));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+1
-12
@@ -28,7 +28,6 @@ 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;
|
||||
@@ -43,7 +42,6 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||
/**
|
||||
* Test case for {@link AlertDefineController}
|
||||
*/
|
||||
@Disabled
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class AlertDefineControllerTest {
|
||||
|
||||
@@ -66,6 +64,7 @@ class AlertDefineControllerTest {
|
||||
|
||||
this.alertDefine = AlertDefine.builder()
|
||||
.id(1L)
|
||||
.name("alertDefine")
|
||||
.expr("1 > 0")
|
||||
.times(1)
|
||||
.template("template")
|
||||
@@ -138,14 +137,4 @@ 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();
|
||||
}
|
||||
}
|
||||
|
||||
+16
-24
@@ -19,7 +19,6 @@ 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;
|
||||
@@ -31,7 +30,6 @@ 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;
|
||||
@@ -50,7 +48,6 @@ 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 {
|
||||
|
||||
@@ -93,8 +90,7 @@ class AlertDefinesControllerTest {
|
||||
pageRequest = PageRequest.of((Integer) content.get("pageIndex"), (Integer) content.get("pageSize"), sortExp);
|
||||
}
|
||||
|
||||
// @Test
|
||||
// todo: fix this test
|
||||
@Test
|
||||
void getAlertDefines() throws Exception {
|
||||
|
||||
// Test the correctness of the mock
|
||||
@@ -112,31 +108,27 @@ class AlertDefinesControllerTest {
|
||||
// }
|
||||
// }))).thenReturn(new PageImpl<AlertDefine>(new ArrayList<AlertDefine>()));
|
||||
AlertDefine define = AlertDefine.builder().id(9L).expr("x").times(1).build();
|
||||
Mockito.when(alertDefineService.getAlertDefines(null, null, "id", "desc", 1, 10)).thenReturn(new PageImpl<>(Collections.singletonList(define)));
|
||||
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));
|
||||
|
||||
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", pageIndex.toString())
|
||||
.param("pageSize", pageSize.toString()))
|
||||
.param("pageIndex", String.valueOf(pageIndex))
|
||||
.param("pageSize", String.valueOf(pageSize)))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
|
||||
.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))
|
||||
.andExpect(jsonPath("$.data.content[0].id").value(9))
|
||||
.andExpect(jsonPath("$.data.content[0].expr").value("x"))
|
||||
.andExpect(jsonPath("$.data.content[0].times").value(1))
|
||||
.andReturn();
|
||||
}
|
||||
|
||||
|
||||
+6
-8
@@ -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,7 +44,6 @@ import org.springframework.test.web.servlet.MockMvc;
|
||||
/**
|
||||
* test case for {@link AlertGroupConvergeController}
|
||||
*/
|
||||
@Disabled
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class AlertGroupConvergeControllerTest {
|
||||
|
||||
@@ -79,8 +78,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"));
|
||||
}
|
||||
@@ -93,8 +92,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"));
|
||||
}
|
||||
@@ -119,7 +118,6 @@ public class AlertGroupConvergeControllerTest {
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value((int) CommonConstants.MONITOR_NOT_EXIST_CODE))
|
||||
.andExpect(jsonPath("$.msg").value("AlertGroupConverge not exist."));
|
||||
.andExpect(jsonPath("$.msg").value("Alert Group Converge not exist."));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
-4
@@ -33,7 +33,6 @@ 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;
|
||||
@@ -52,7 +51,6 @@ import org.springframework.test.web.servlet.MockMvc;
|
||||
*/
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@Disabled
|
||||
class AlertGroupConvergesControllerTest {
|
||||
|
||||
private MockMvc mockMvc;
|
||||
@@ -101,6 +99,7 @@ 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))
|
||||
@@ -118,5 +117,4 @@ class AlertGroupConvergesControllerTest {
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+114
-24
@@ -17,7 +17,6 @@
|
||||
|
||||
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;
|
||||
@@ -44,6 +43,10 @@ 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;
|
||||
@@ -168,12 +171,38 @@ class NoticeConfigControllerTest {
|
||||
|
||||
@Test
|
||||
void getReceivers() throws Exception {
|
||||
NoticeReceiver receiver1 = new NoticeReceiver();
|
||||
receiver1.setId(1L);
|
||||
receiver1.setName("Receiver1");
|
||||
|
||||
//Mockito.when(noticeConfigService.getNoticeReceivers())
|
||||
this.mockMvc.perform(MockMvcRequestBuilders.get("/api/notice/receivers?name={name}", "tom"))
|
||||
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))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
|
||||
.andReturn();
|
||||
.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));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -245,15 +274,38 @@ class NoticeConfigControllerTest {
|
||||
|
||||
@Test
|
||||
void getRules() throws Exception {
|
||||
this.mockMvc.perform(MockMvcRequestBuilders.get("/api/notice/rules"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
|
||||
.andReturn();
|
||||
NoticeRule rule1 = new NoticeRule();
|
||||
rule1.setId(1L);
|
||||
rule1.setName("Rule1");
|
||||
|
||||
this.mockMvc.perform(MockMvcRequestBuilders.get("/api/notice/rules?name={name}", "tom"))
|
||||
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))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
|
||||
.andReturn();
|
||||
.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));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -362,22 +414,40 @@ class NoticeConfigControllerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetTemplates() throws Exception {
|
||||
// Mock the service response
|
||||
void getTemplates() throws Exception {
|
||||
NoticeTemplate template1 = new NoticeTemplate();
|
||||
template1.setId(1L);
|
||||
template1.setName("Template1");
|
||||
NoticeTemplate template2 = new NoticeTemplate();
|
||||
template2.setName("Template2");
|
||||
List<NoticeTemplate> templates = Arrays.asList(template1, template2);
|
||||
when(noticeConfigService.getNoticeTemplates(any())).thenReturn(templates);
|
||||
|
||||
// Perform the GET request and verify the response
|
||||
this.mockMvc.perform(get("/api/notice/templates")
|
||||
.param("name", "Template"))
|
||||
NoticeTemplate template2 = new NoticeTemplate();
|
||||
template2.setId(2L);
|
||||
template2.setName("Template2");
|
||||
|
||||
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))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
|
||||
.andExpect(jsonPath("$.data[0].name").value("Template1"))
|
||||
.andExpect(jsonPath("$.data[1].name").value("Template2"));
|
||||
.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));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -414,5 +484,25 @@ 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();
|
||||
}
|
||||
}
|
||||
|
||||
+6
-1
@@ -27,6 +27,7 @@ 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;
|
||||
@@ -60,6 +61,9 @@ class AlertNoticeDispatchTest {
|
||||
@Mock
|
||||
private AlertNotifyHandler alertNotifyHandler;
|
||||
|
||||
@Mock
|
||||
private AlertSseManager emitterManager;
|
||||
|
||||
private AlertNoticeDispatch alertNoticeDispatch;
|
||||
|
||||
private static final int DISPATCH_THREADS = 3;
|
||||
@@ -77,7 +81,8 @@ class AlertNoticeDispatchTest {
|
||||
noticeConfigService,
|
||||
alertStoreHandler,
|
||||
alertNotifyHandlerList,
|
||||
pluginRunner
|
||||
pluginRunner,
|
||||
emitterManager
|
||||
);
|
||||
|
||||
receiver = NoticeReceiver.builder()
|
||||
|
||||
+19
-10
@@ -27,7 +27,6 @@ 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;
|
||||
@@ -39,7 +38,6 @@ import java.util.List;
|
||||
/**
|
||||
* Test case for {@link DbAlertStoreHandlerImpl}
|
||||
*/
|
||||
@Disabled
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class DbAlertStoreHandlerImplTest {
|
||||
|
||||
@@ -75,27 +73,34 @@ 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");
|
||||
@@ -103,11 +108,15 @@ 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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+11
-32
@@ -30,18 +30,17 @@ 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 AlarmSilenceReduce
|
||||
* Test for {@link AlarmSilenceReduce}
|
||||
*/
|
||||
@Disabled
|
||||
class AlarmSilenceReduceTest {
|
||||
|
||||
@Mock
|
||||
@@ -55,21 +54,23 @@ class AlarmSilenceReduceTest {
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
MockitoAnnotations.openMocks(this);
|
||||
when(alertSilenceDao.findAll()).thenReturn(Collections.emptyList());
|
||||
CacheFactory.clearAlertSilenceCache();
|
||||
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)
|
||||
@@ -81,7 +82,7 @@ class AlarmSilenceReduceTest {
|
||||
.times(0)
|
||||
.build();
|
||||
|
||||
when(alertSilenceDao.findAll()).thenReturn(Collections.singletonList(silenceRule));
|
||||
when(alertSilenceDao.findAlertSilencesByEnableTrue()).thenReturn(Collections.singletonList(silenceRule));
|
||||
when(alertSilenceDao.save(any(AlertSilence.class))).thenReturn(silenceRule);
|
||||
|
||||
GroupAlert alert = createGroupAlert("firing", createLabels("service", "web"));
|
||||
@@ -107,9 +108,9 @@ class AlarmSilenceReduceTest {
|
||||
.times(0)
|
||||
.build();
|
||||
|
||||
when(alertSilenceDao.findAll()).thenReturn(Collections.singletonList(silenceRule));
|
||||
when(alertSilenceDao.findAlertSilencesByEnableTrue()).thenReturn(Collections.singletonList(silenceRule));
|
||||
when(alertSilenceDao.save(any(AlertSilence.class))).thenReturn(silenceRule);
|
||||
|
||||
|
||||
GroupAlert alert = createGroupAlert("firing", createLabels("service", "web"));
|
||||
|
||||
alarmSilenceReduce.silenceAlarm(alert);
|
||||
@@ -130,29 +131,7 @@ class AlarmSilenceReduceTest {
|
||||
.times(0)
|
||||
.build();
|
||||
|
||||
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));
|
||||
when(alertSilenceDao.findAlertSilencesByEnableTrue()).thenReturn(Collections.singletonList(silenceRule));
|
||||
|
||||
GroupAlert alert = createGroupAlert("firing", createLabels("service", "web"));
|
||||
|
||||
|
||||
+35
-25
@@ -24,16 +24,18 @@ 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;
|
||||
@@ -42,7 +44,6 @@ import org.mockito.junit.jupiter.MockitoExtension;
|
||||
/**
|
||||
* test case for {@link AlertDefineExcelImExportServiceImpl}
|
||||
*/
|
||||
@Disabled
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class AlertDefineExcelImExportServiceTest {
|
||||
|
||||
@@ -61,15 +62,13 @@ public class AlertDefineExcelImExportServiceTest {
|
||||
Row row = initialSheet.createRow(1);
|
||||
row.createCell(0).setCellValue("app1");
|
||||
row.createCell(1).setCellValue("metric1");
|
||||
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(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(8).setCellValue(true);
|
||||
row.createCell(9).setCellValue(true);
|
||||
row.createCell(10).setCellValue("template1");
|
||||
|
||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(toByteArray(initialWorkbook));
|
||||
|
||||
@@ -88,9 +87,12 @@ public class AlertDefineExcelImExportServiceTest {
|
||||
assertEquals("app1", alertDefineDTO.getName());
|
||||
assertEquals("metric1", alertDefineDTO.getType());
|
||||
assertEquals("expr1", alertDefineDTO.getExpr());
|
||||
assertEquals(10, alertDefineDTO.getTimes());
|
||||
assertTrue(alertDefineDTO.getEnable());
|
||||
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());
|
||||
assertTrue(alertDefineDTO.getEnable());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,9 +105,12 @@ public class AlertDefineExcelImExportServiceTest {
|
||||
alertDefineDTO.setName("app1");
|
||||
alertDefineDTO.setType("metric1");
|
||||
alertDefineDTO.setExpr("expr1");
|
||||
alertDefineDTO.setTimes(10);
|
||||
alertDefineDTO.setEnable(true);
|
||||
alertDefineDTO.setPeriod(10);
|
||||
alertDefineDTO.setTimes(1);
|
||||
alertDefineDTO.setLabels(Map.of("key", "value"));
|
||||
alertDefineDTO.setAnnotations(Map.of("key", "value"));
|
||||
alertDefineDTO.setTemplate("template1");
|
||||
alertDefineDTO.setEnable(true);
|
||||
exportAlertDefineDTO.setAlertDefine(alertDefineDTO);
|
||||
exportAlertDefineList.add(exportAlertDefineDTO);
|
||||
|
||||
@@ -115,21 +120,26 @@ public class AlertDefineExcelImExportServiceTest {
|
||||
try (Workbook resultWorkbook = WorkbookFactory.create(new ByteArrayInputStream(outputStream.toByteArray()))) {
|
||||
Sheet resultSheet = resultWorkbook.getSheetAt(0);
|
||||
Row headerRow = resultSheet.getRow(0);
|
||||
assertEquals("app", headerRow.getCell(0).getStringCellValue());
|
||||
assertEquals("metric", headerRow.getCell(1).getStringCellValue());
|
||||
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());
|
||||
|
||||
Row dataRow = resultSheet.getRow(1);
|
||||
assertEquals("app1", dataRow.getCell(0).getStringCellValue());
|
||||
assertEquals("metric1", dataRow.getCell(1).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());
|
||||
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());
|
||||
assertTrue(dataRow.getCell(8).getBooleanCellValue());
|
||||
assertTrue(dataRow.getCell(9).getBooleanCellValue());
|
||||
assertEquals("template1", dataRow.getCell(10).getStringCellValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -142,4 +152,4 @@ public class AlertDefineExcelImExportServiceTest {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+5
@@ -30,6 +30,7 @@ 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;
|
||||
@@ -54,6 +55,9 @@ class AlertDefineServiceTest {
|
||||
|
||||
@Mock
|
||||
private AlertDefineDao alertDefineDao;
|
||||
|
||||
@Mock
|
||||
private PeriodicAlertRuleScheduler periodicAlertRuleScheduler;
|
||||
|
||||
@Mock
|
||||
private List<AlertDefineImExportService> alertDefineImExportServiceList;
|
||||
@@ -64,6 +68,7 @@ class AlertDefineServiceTest {
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
ReflectionTestUtils.setField(this.alertDefineService, "alertDefineDao", alertDefineDao);
|
||||
ReflectionTestUtils.setField(this.alertDefineService, "periodicAlertRuleScheduler", periodicAlertRuleScheduler);
|
||||
|
||||
this.alertDefine = AlertDefine.builder()
|
||||
.id(1L)
|
||||
|
||||
+547
@@ -0,0 +1,547 @@
|
||||
/*
|
||||
* 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__"));
|
||||
}
|
||||
}
|
||||
+130
-17
@@ -17,33 +17,42 @@
|
||||
|
||||
package org.apache.hertzbeat.alert.service;
|
||||
|
||||
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 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.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.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;
|
||||
|
||||
|
||||
/**
|
||||
* Test case for {@link NoticeConfigService}
|
||||
*/
|
||||
@Disabled
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class NoticeConfigServiceTest {
|
||||
|
||||
@@ -58,26 +67,130 @@ 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() {
|
||||
noticeConfigService.getNoticeReceivers(null);
|
||||
verify(noticeReceiverDao, times(1)).findAll(any(Specification.class));
|
||||
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();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getNoticeTemplates() {
|
||||
noticeConfigService.getNoticeTemplates(null);
|
||||
verify(noticeTemplateDao, times(1)).findAll(any(Specification.class));
|
||||
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();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getNoticeRules() {
|
||||
noticeConfigService.getNoticeRules(null);
|
||||
verify(noticeRuleDao, times(1)).findAll(any(Specification.class));
|
||||
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));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+45
-50
@@ -17,68 +17,63 @@
|
||||
|
||||
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() {
|
||||
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();
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
@Test
|
||||
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();
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
+3
@@ -18,6 +18,7 @@
|
||||
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;
|
||||
@@ -97,10 +98,12 @@ 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
|
||||
|
||||
+3
@@ -18,6 +18,7 @@
|
||||
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;
|
||||
@@ -100,10 +101,12 @@ 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
|
||||
|
||||
@@ -27,6 +27,7 @@ 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.
|
||||
@@ -39,6 +40,7 @@ import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
|
||||
@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);
|
||||
|
||||
+1
-1
@@ -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(root.get("name"), "%" + name + "%");
|
||||
Predicate predicateName = criteriaBuilder.like(criteriaBuilder.lower(root.get("name")), "%" + name.toLowerCase() + "%");
|
||||
predicate = criteriaBuilder.and(predicateName);
|
||||
}
|
||||
return predicate;
|
||||
|
||||
+1
-1
@@ -566,7 +566,7 @@ 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(root.get("name"), "%" + search + "%");
|
||||
Predicate predicateName = criteriaBuilder.like(criteriaBuilder.lower(root.get("name")), "%" + search.toLowerCase() + "%");
|
||||
Predicate predicateId = criteriaBuilder.like(root.get("id"), "%" + search + "%");
|
||||
orList.add(predicateHost);
|
||||
orList.add(predicateName);
|
||||
|
||||
+2
-2
@@ -92,9 +92,9 @@ public class TagServiceImpl implements TagService {
|
||||
|
||||
List<Predicate> orList = new ArrayList<>();
|
||||
if (StringUtils.isNotBlank(search)) {
|
||||
Predicate predicateName = criteriaBuilder.like(root.get("name"), "%" + search + "%");
|
||||
Predicate predicateName = criteriaBuilder.like(criteriaBuilder.lower(root.get("name")), "%" + search.toLowerCase() + "%");
|
||||
orList.add(predicateName);
|
||||
Predicate predicateValue = criteriaBuilder.like(root.get("tagValue"), "%" + search + "%");
|
||||
Predicate predicateValue = criteriaBuilder.like(criteriaBuilder.lower(root.get("tagValue")), "%" + search.toLowerCase() + "%");
|
||||
orList.add(predicateValue);
|
||||
}
|
||||
Predicate[] orPredicates = new Predicate[orList.size()];
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
# limitations under the License.
|
||||
|
||||
# The monitoring type category:service-application service monitoring db-database monitoring custom-custom monitoring os-operating system monitoring
|
||||
category: server
|
||||
category: llm
|
||||
# The monitoring type eg: linux windows tomcat mysql aws...
|
||||
app: nvidia
|
||||
# The monitoring i18n name
|
||||
|
||||
@@ -72,6 +72,7 @@ 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
|
||||
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
+12
-16
@@ -18,8 +18,11 @@
|
||||
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;
|
||||
@@ -27,7 +30,6 @@ 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;
|
||||
|
||||
@@ -44,6 +46,8 @@ 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,
|
||||
@@ -87,24 +91,16 @@ 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 {
|
||||
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);
|
||||
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);
|
||||
}
|
||||
} catch (EmptyResultDataAccessException ignored) {
|
||||
// when query currentStatus result is null
|
||||
} catch (Exception e) {
|
||||
log.error("Update monitor status failed for monitor id: {}", id, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ 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);
|
||||
@@ -49,10 +48,5 @@ export class AppComponent implements OnInit {
|
||||
this.modalSrv.closeAll();
|
||||
}
|
||||
});
|
||||
// set theme
|
||||
const storedTheme = localStorage.getItem('theme');
|
||||
if (storedTheme) {
|
||||
this.themeService.changeTheme(storedTheme);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ 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({
|
||||
@@ -28,7 +29,8 @@ export class StartupService {
|
||||
@Inject(DA_SERVICE_TOKEN) private tokenService: ITokenService,
|
||||
private httpClient: HttpClient,
|
||||
private router: Router,
|
||||
private storageService: MemoryStorageService
|
||||
private storageService: MemoryStorageService,
|
||||
private themeService: ThemeService
|
||||
) {
|
||||
iconSrv.addIcon(...ICONS_AUTO, ...ICONS);
|
||||
}
|
||||
@@ -86,6 +88,7 @@ export class StartupService {
|
||||
this.storageService.putData('hierarchy', menuData.data);
|
||||
this.menuService.resume();
|
||||
this.titleService.suffix = appData.app.name;
|
||||
this.themeService.changeTheme(null);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@@ -41,9 +41,6 @@ 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>
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
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,6 +7,7 @@ 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';
|
||||
@@ -122,6 +123,7 @@ 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,
|
||||
@@ -154,9 +156,7 @@ export class HeaderNotifyComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
);
|
||||
this.loadData();
|
||||
this.refreshInterval = setInterval(() => {
|
||||
this.loadData();
|
||||
}, 10000); // every 10 seconds refresh the tabs
|
||||
this.initSSEConnection();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
@@ -207,7 +207,6 @@ 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',
|
||||
@@ -217,11 +216,6 @@ 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);
|
||||
@@ -291,4 +285,36 @@ 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,12 +129,7 @@ export class HeaderUserComponent {
|
||||
}
|
||||
|
||||
logout(): void {
|
||||
let tmp = this.localStorageSvc.getData(this.notShowAgainKey);
|
||||
if (tmp === null) {
|
||||
tmp = 'false';
|
||||
}
|
||||
this.localStorageSvc.clear();
|
||||
this.localStorageSvc.putData(this.notShowAgainKey, tmp);
|
||||
this.localStorageSvc.clearAuthorization();
|
||||
this.router.navigateByUrl('/passport/login');
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@ 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';
|
||||
@@ -33,7 +32,6 @@ const HEADER_COMPONENTS = [
|
||||
HeaderSearchComponent,
|
||||
HeaderFullScreenComponent,
|
||||
HeaderI18nComponent,
|
||||
HeaderClearStorageComponent,
|
||||
HeaderUserComponent,
|
||||
HeaderNotifyComponent
|
||||
];
|
||||
|
||||
@@ -29,10 +29,6 @@
|
||||
<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
|
||||
@@ -63,7 +59,13 @@
|
||||
</app-toolbar>
|
||||
|
||||
<div class="alert-cards">
|
||||
<nz-card *ngFor="let group of groupAlerts" class="alert-card" [class]="'status-' + group.status" [nzBordered]="false">
|
||||
<nz-card
|
||||
*ngFor="let group of groupAlerts"
|
||||
class="alert-card"
|
||||
[class.new-alert]="group.isNew"
|
||||
[class]="'status-' + group.status"
|
||||
[nzBordered]="false"
|
||||
>
|
||||
<!-- Alert Group Header -->
|
||||
<div class="alert-header">
|
||||
<div class="alert-info">
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
/* 调整工具栏布局 */
|
||||
:host ::ng-deep app-toolbar {
|
||||
.center-content {
|
||||
display: flex;
|
||||
@@ -68,17 +67,50 @@
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
padding: 16px;
|
||||
transform-style: preserve-3d;
|
||||
perspective: 1200px;
|
||||
}
|
||||
|
||||
:root {
|
||||
--background-color: #fff;
|
||||
--text-color: #333;
|
||||
}
|
||||
|
||||
[data-theme='dark'] {
|
||||
--background-color: #1e1e1e;
|
||||
--text-color: #fff;
|
||||
}
|
||||
|
||||
.alert-card {
|
||||
background: #fff;
|
||||
position: relative;
|
||||
background: var(--background-color);
|
||||
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 {
|
||||
@@ -90,7 +122,7 @@
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
||||
|
||||
.alert-info {
|
||||
flex: 1;
|
||||
@@ -110,7 +142,7 @@
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
margin-top: 8px;
|
||||
color: #8c8c8c;
|
||||
color: rgba(0, 0, 0, 0.5);
|
||||
font-size: 12px;
|
||||
|
||||
i {
|
||||
@@ -143,16 +175,22 @@
|
||||
|
||||
.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 #f0f0f0;
|
||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
margin-bottom: 8px;
|
||||
position: relative;
|
||||
z-index: 6;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
@@ -162,9 +200,11 @@
|
||||
padding: 8px 12px;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
z-index: 7;
|
||||
|
||||
&:hover {
|
||||
background-color: #fafafa;
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.ant-collapse-header-text {
|
||||
@@ -173,19 +213,21 @@
|
||||
|
||||
.ant-collapse-extra {
|
||||
margin: 0;
|
||||
color: #8c8c8c;
|
||||
color: rgba(0, 0, 0, 0.5);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.alert-content {
|
||||
font-size: 13px;
|
||||
color: #333;
|
||||
color: var(--text-color);
|
||||
line-height: 1.5;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-collapse-content {
|
||||
border-top: 1px solid #f0f0f0;
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.1);
|
||||
position: relative;
|
||||
z-index: 6;
|
||||
|
||||
.ant-collapse-content-box {
|
||||
padding: 12px;
|
||||
@@ -269,16 +311,86 @@
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.alert-card {
|
||||
&.status-firing {
|
||||
border-left-color: #ff4d4f;
|
||||
@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);
|
||||
}
|
||||
|
||||
&.status-resolved {
|
||||
border-left-color: #52c41a;
|
||||
50% {
|
||||
opacity: 1;
|
||||
filter: blur(0);
|
||||
transform: translate3d(-5%, 0, 0) scale(1) rotate(-1deg);
|
||||
}
|
||||
|
||||
&.status-pending {
|
||||
border-left-color: #faad14;
|
||||
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);
|
||||
}
|
||||
|
||||
&::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;
|
||||
}
|
||||
|
||||
&.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; }
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { Component, Inject, OnInit } from '@angular/core';
|
||||
import { Component, Inject, OnDestroy, OnInit } from '@angular/core';
|
||||
import { I18NService } from '@core';
|
||||
import { ALAIN_I18N_TOKEN } from '@delon/theme';
|
||||
import { NzModalService } from 'ng-zorro-antd/modal';
|
||||
@@ -26,12 +26,15 @@ 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 {
|
||||
export class AlertCenterComponent implements OnInit, OnDestroy {
|
||||
constructor(
|
||||
private notifySvc: NzNotificationService,
|
||||
private modal: NzModalService,
|
||||
@@ -42,18 +45,109 @@ export class AlertCenterComponent implements OnInit {
|
||||
pageIndex: number = 1;
|
||||
pageSize: number = 8;
|
||||
total: number = 0;
|
||||
groupAlerts!: GroupAlert[];
|
||||
groupAlerts: ExtendedGroupAlert[] = [];
|
||||
tableLoading: boolean = false;
|
||||
checkedAlertIds = new Set<number>();
|
||||
filterStatus!: string;
|
||||
filterContent: string | undefined;
|
||||
private eventSource!: EventSource;
|
||||
|
||||
ngOnInit(): void {
|
||||
this.loadAlertsTable();
|
||||
this.initSSESubscription();
|
||||
}
|
||||
|
||||
sync() {
|
||||
this.loadAlertsTable();
|
||||
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;
|
||||
}
|
||||
|
||||
loadAlertsTable() {
|
||||
|
||||
@@ -1,20 +1,32 @@
|
||||
:root {
|
||||
--background-color: #fff;
|
||||
--text-color: #333;
|
||||
}
|
||||
|
||||
[data-theme='dark'] {
|
||||
--background-color: #1e1e1e;
|
||||
--text-color: #fff;
|
||||
}
|
||||
|
||||
.alert-integration-container {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
background: #fff;
|
||||
background: var(--background-color);
|
||||
border-radius: 4px;
|
||||
|
||||
|
||||
.data-sources {
|
||||
width: 240px;
|
||||
border-right: 1px solid #f0f0f0;
|
||||
padding: 16px;
|
||||
|
||||
background: var(--background-color);
|
||||
|
||||
h2 {
|
||||
margin-bottom: 16px;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
|
||||
.source-list {
|
||||
.source-item {
|
||||
display: flex;
|
||||
@@ -23,37 +35,53 @@
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
transition: all 0.3s;
|
||||
|
||||
|
||||
&:hover {
|
||||
background: #f5f5f5;
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
|
||||
&.active {
|
||||
background: #e6f7ff;
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
|
||||
img {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
|
||||
span {
|
||||
color: #333;
|
||||
color: var(--text-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.doc-content {
|
||||
flex: 1;
|
||||
padding: 24px;
|
||||
overflow-y: auto;
|
||||
|
||||
background: var(--background-color);
|
||||
|
||||
h2 {
|
||||
margin-bottom: 24px;
|
||||
font-size: 20px;
|
||||
font-weight: 500;
|
||||
color: var(--text-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[data-theme='dark'] {
|
||||
.source-list {
|
||||
.source-item {
|
||||
&:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+26
-1
@@ -28,8 +28,30 @@
|
||||
{{ '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 [nzData]="receivers" [nzLoading]="receiverTableLoading" [nzScroll]="{ x: '1240px' }" nzFrontPagination="false">
|
||||
<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' }"
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th nzAlign="center" nzWidth="15%">{{ 'alert.notice.receiver.people' | i18n }}</th>
|
||||
@@ -144,6 +166,9 @@
|
||||
</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()"
|
||||
|
||||
+28
-2
@@ -23,6 +23,7 @@ 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';
|
||||
@@ -41,6 +42,10 @@ 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(
|
||||
@@ -60,11 +65,14 @@ export class AlertNoticeReceiverComponent implements OnInit {
|
||||
|
||||
loadReceiversTable() {
|
||||
this.receiverTableLoading = true;
|
||||
let receiverInit$ = this.noticeReceiverSvc.getReceivers().subscribe(
|
||||
let receiverInit$ = this.noticeReceiverSvc.getReceivers(this.name, this.pageIndex - 1, this.pageSize).subscribe(
|
||||
message => {
|
||||
this.receiverTableLoading = false;
|
||||
if (message.code === 0) {
|
||||
this.receivers = message.data;
|
||||
let page = message.data;
|
||||
this.receivers = page.content;
|
||||
this.total = page.totalElements;
|
||||
this.pageIndex = page.number + 1;
|
||||
} else {
|
||||
console.warn(message.msg);
|
||||
}
|
||||
@@ -102,6 +110,7 @@ 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);
|
||||
@@ -113,6 +122,11 @@ 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) {
|
||||
@@ -265,4 +279,16 @@ 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();
|
||||
}
|
||||
}
|
||||
|
||||
+25
-1
@@ -28,8 +28,30 @@
|
||||
{{ '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 [nzData]="rules" [nzLoading]="ruleTableLoading" [nzScroll]="{ x: '1240px' }" nzFrontPagination="false">
|
||||
<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' }"
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th nzAlign="center" nzWidth="15%">{{ 'alert.notice.rule.name' | i18n }}</th>
|
||||
@@ -88,6 +110,8 @@
|
||||
</tbody>
|
||||
</nz-table>
|
||||
|
||||
<ng-template #rangeTemplate> {{ 'common.total' | i18n }} {{ total }} </ng-template>
|
||||
|
||||
<!-- new or update notice strategy pop-up box -->
|
||||
<nz-modal
|
||||
(nzOnCancel)="onManageRuleModalCancel()"
|
||||
|
||||
+30
-4
@@ -23,6 +23,7 @@ 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';
|
||||
@@ -47,6 +48,10 @@ 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 = [
|
||||
@@ -78,11 +83,14 @@ export class AlertNoticeRuleComponent implements OnInit {
|
||||
|
||||
loadRulesTable() {
|
||||
this.ruleTableLoading = true;
|
||||
let rulesInit$ = this.noticeRuleSvc.getNoticeRules().subscribe(
|
||||
let rulesInit$ = this.noticeRuleSvc.getNoticeRules(this.name, this.pageIndex - 1, this.pageSize).subscribe(
|
||||
message => {
|
||||
this.ruleTableLoading = false;
|
||||
if (message.code === 0) {
|
||||
this.rules = message.data;
|
||||
let page = message.data;
|
||||
this.rules = page.content;
|
||||
this.total = page.totalElements;
|
||||
this.pageIndex = page.number + 1;
|
||||
} else {
|
||||
console.warn(message.msg);
|
||||
}
|
||||
@@ -122,6 +130,7 @@ 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);
|
||||
@@ -133,6 +142,11 @@ 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;
|
||||
@@ -213,7 +227,7 @@ export class AlertNoticeRuleComponent implements OnInit {
|
||||
}
|
||||
|
||||
loadReceiversOption() {
|
||||
let receiverOption$ = this.noticeReceiverSvc.getReceivers().subscribe(
|
||||
let receiverOption$ = this.noticeReceiverSvc.getAllReceivers().subscribe(
|
||||
message => {
|
||||
if (message.code === 0) {
|
||||
let data = message.data;
|
||||
@@ -282,7 +296,7 @@ export class AlertNoticeRuleComponent implements OnInit {
|
||||
}
|
||||
|
||||
loadTemplatesOption() {
|
||||
let templateOption$ = this.noticeTemplateSvc.getNoticeTemplates().subscribe(
|
||||
let templateOption$ = this.noticeTemplateSvc.getAllNoticeTemplates().subscribe(
|
||||
message => {
|
||||
if (message.code === 0) {
|
||||
let data = message.data;
|
||||
@@ -426,4 +440,16 @@ 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();
|
||||
}
|
||||
}
|
||||
|
||||
+23
@@ -27,9 +27,29 @@
|
||||
{{ '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' }"
|
||||
@@ -142,6 +162,9 @@
|
||||
</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()"
|
||||
|
||||
+34
-3
@@ -23,6 +23,7 @@ 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';
|
||||
@@ -43,6 +44,11 @@ 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(
|
||||
@@ -62,12 +68,14 @@ export class AlertNoticeTemplateComponent implements OnInit {
|
||||
|
||||
loadTemplatesTable() {
|
||||
this.templateTableLoading = true;
|
||||
let templatesInit$ = this.noticeTemplateSvc.getNoticeTemplates().subscribe(
|
||||
let templatesInit$ = this.noticeTemplateSvc.getNoticeTemplates(this.name, this.preset, this.pageIndex - 1, this.pageSize).subscribe(
|
||||
message => {
|
||||
this.templateTableLoading = false;
|
||||
if (message.code === 0) {
|
||||
this.templates = message.data;
|
||||
// this.templates=this.templates.concat(this.defaultTemplates);
|
||||
let page = message.data;
|
||||
this.templates = page.content;
|
||||
this.total = page.totalElements;
|
||||
this.pageIndex = page.number + 1;
|
||||
} else {
|
||||
console.warn(message.msg);
|
||||
}
|
||||
@@ -105,6 +113,7 @@ 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);
|
||||
@@ -116,6 +125,11 @@ 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;
|
||||
@@ -209,4 +223,21 @@ 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,6 +70,7 @@
|
||||
[placeholder]="'alert.setting.search' | i18n"
|
||||
[(value)]="search"
|
||||
(keydown.enter)="onFilterChange()"
|
||||
(cleared)="onFilterChange()"
|
||||
/>
|
||||
</ng-template>
|
||||
</app-toolbar>
|
||||
@@ -417,13 +418,13 @@
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
<nz-form-item *ngIf="define.type == 'periodic'" [ngStyle]="{ marginBottom: '5px' }">
|
||||
<nz-form-label [nzSpan]="7" nzFor="promql" nzRequired="true" [nzTooltipTitle]="'alert.setting.rule.label' | i18n">
|
||||
<nz-form-label [nzSpan]="7" nzFor="datasource" 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" id="promql">
|
||||
<label nz-radio-button [nzValue]="'periodic'">
|
||||
<nz-radio-group [(ngModel)]="define.datasource" nzButtonStyle="solid" name="datasource" id="datasource">
|
||||
<label nz-radio-button [nzValue]="'promql'">
|
||||
{{ 'PromQL' | i18n }}
|
||||
</label>
|
||||
</nz-radio-group>
|
||||
@@ -476,6 +477,7 @@
|
||||
[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>
|
||||
@@ -498,7 +500,7 @@
|
||||
</nz-form-label>
|
||||
<nz-form-control [nzSpan]="12" [nzErrorTip]="'validation.required' | i18n">
|
||||
<div class="template-input-wrapper">
|
||||
<div class="draggable-vars-container">
|
||||
<div *ngIf="define.type === 'realtime'" 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 !== undefined && this.search.trim() !== '') {
|
||||
if (this.search && this.search.trim() !== '') {
|
||||
trimSearch = this.search.trim();
|
||||
}
|
||||
// Filter entries based on search input
|
||||
@@ -240,6 +240,7 @@ 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
|
||||
|
||||
@@ -113,7 +113,7 @@
|
||||
<nz-table
|
||||
*ngIf="!monitor && isTable"
|
||||
nzSize="small"
|
||||
nzNoResult="No Metrics Data"
|
||||
[nzNoResult]="'monitor.detail.chart.no-data' | i18n"
|
||||
[nzFrontPagination]="false"
|
||||
[nzShowPagination]="false"
|
||||
[nzData]="valueRows"
|
||||
@@ -143,7 +143,7 @@
|
||||
<nz-table
|
||||
*ngIf="!monitor && !isTable"
|
||||
nzSize="small"
|
||||
nzNoResult="No Metrics Data"
|
||||
[nzNoResult]="'monitor.detail.chart.no-data' | i18n"
|
||||
[nzFrontPagination]="false"
|
||||
[nzShowPagination]="false"
|
||||
[nzData]="valueRows"
|
||||
|
||||
@@ -120,7 +120,7 @@
|
||||
<app-multi-func-input
|
||||
groupStyle="width: 120px;"
|
||||
class="mobile-hide"
|
||||
[placeholder]="'monitor.search.tag' | i18n"
|
||||
[placeholder]="'monitor.search.label' | i18n"
|
||||
[(value)]="labels"
|
||||
(valueChange)="onTagChanged()"
|
||||
/>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
~ under the License.
|
||||
-->
|
||||
|
||||
<div class="br-8" style="background-color: snow; padding: 20px; box-shadow: 7px 5px #b421cc">
|
||||
<div class="br-8" style="background-color: rgb(198 189 189 / 39%); 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">
|
||||
|
||||
@@ -58,7 +58,7 @@ export class SystemConfigComponent implements OnInit {
|
||||
if (message.code === 0) {
|
||||
if (message.data) {
|
||||
this.config = message.data;
|
||||
this.changeTheme(this.config.theme); // update theme after config is loaded
|
||||
this.config.theme = this.themeService.getTheme() || 'default';
|
||||
} else {
|
||||
this.config = new SystemConfig();
|
||||
}
|
||||
@@ -94,6 +94,7 @@ 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 {
|
||||
@@ -105,8 +106,4 @@ export class SystemConfigComponent implements OnInit {
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
changeTheme(theme: string): void {
|
||||
this.themeService.changeTheme(theme);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
@import '@delon/theme/index';
|
||||
|
||||
.tag-cards-container {
|
||||
padding: 24px 0;
|
||||
|
||||
.tag-card {
|
||||
transition: all 0.3s;
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
@@ -23,8 +24,7 @@
|
||||
|
||||
.ant-card-actions {
|
||||
border-radius: 0 0 8px 8px;
|
||||
background: #fafafa;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
border-top: 1px solid rgba(240, 240, 240, 0.5);
|
||||
min-height: 32px;
|
||||
|
||||
> li {
|
||||
@@ -34,7 +34,7 @@
|
||||
padding: 4px 0;
|
||||
|
||||
&:hover {
|
||||
color: #1890ff;
|
||||
color: @primary-color;
|
||||
}
|
||||
|
||||
i {
|
||||
@@ -60,6 +60,7 @@
|
||||
font-weight: 500;
|
||||
line-height: 1.4;
|
||||
text-align: center;
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -57,7 +57,8 @@ export class LocalStorageService {
|
||||
return localStorage.getItem(AuthorizationConst) != null;
|
||||
}
|
||||
|
||||
public clear() {
|
||||
localStorage.clear();
|
||||
public clearAuthorization() {
|
||||
localStorage.removeItem(AuthorizationConst);
|
||||
localStorage.removeItem(RefreshTokenConst);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,15 +17,17 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { HttpClient, HttpParams } 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({
|
||||
@@ -46,8 +48,21 @@ export class NoticeReceiverService {
|
||||
return this.http.delete<Message<any>>(`${notice_receiver_uri}/${receiverId}`);
|
||||
}
|
||||
|
||||
public getReceivers(): Observable<Message<NoticeReceiver[]>> {
|
||||
return this.http.get<Message<NoticeReceiver[]>>(notice_receivers_uri);
|
||||
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 getReceiver(receiverId: number): Observable<Message<NoticeReceiver>> {
|
||||
|
||||
@@ -17,12 +17,13 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { HttpClient, HttpParams } 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';
|
||||
@@ -45,8 +46,17 @@ export class NoticeRuleService {
|
||||
return this.http.delete<Message<any>>(`${notice_rule_uri}/${ruleId}`);
|
||||
}
|
||||
|
||||
public getNoticeRules(): Observable<Message<NoticeRule[]>> {
|
||||
return this.http.get<Message<NoticeRule[]>>(notice_rules_uri);
|
||||
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 getNoticeRuleById(ruleId: number): Observable<Message<NoticeRule>> {
|
||||
|
||||
@@ -17,15 +17,17 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { HttpClient, HttpParams } 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'
|
||||
@@ -45,8 +47,22 @@ export class NoticeTemplateService {
|
||||
return this.http.delete<Message<any>>(`${notice_template_uri}/${templateId}`);
|
||||
}
|
||||
|
||||
public getNoticeTemplates(): Observable<Message<NoticeTemplate[]>> {
|
||||
return this.http.get<Message<NoticeTemplate[]>>(notice_templates_uri);
|
||||
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 getDefaultNoticeTemplates(): Observable<Message<NoticeTemplate[]>> {
|
||||
|
||||
@@ -36,11 +36,10 @@ export class ThemeService {
|
||||
return localStorage.getItem(this.themeKey);
|
||||
}
|
||||
|
||||
clearTheme(): void {
|
||||
localStorage.removeItem(this.themeKey);
|
||||
}
|
||||
|
||||
changeTheme(theme: string): void {
|
||||
changeTheme(theme: string | null): void {
|
||||
if (theme == null) {
|
||||
theme = this.getTheme();
|
||||
}
|
||||
const style = this.doc.createElement('link');
|
||||
style.type = 'text/css';
|
||||
style.rel = 'stylesheet';
|
||||
@@ -57,9 +56,6 @@ export class ThemeService {
|
||||
|
||||
const compactDom = this.doc.getElementById('compact-theme');
|
||||
if (compactDom) compactDom.remove();
|
||||
|
||||
this.clearTheme();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -52,6 +52,7 @@ export class MultiFuncInputComponent implements ControlValueAccessor {
|
||||
@Input() type: string = 'text';
|
||||
@Input() size: NzSizeLDSType = 'default';
|
||||
@Output() readonly valueChange = new EventEmitter<string>();
|
||||
@Output() readonly cleared = new EventEmitter<void>();
|
||||
|
||||
disabled: boolean = false;
|
||||
passwordVisible: boolean = false;
|
||||
@@ -67,6 +68,7 @@ export class MultiFuncInputComponent implements ControlValueAccessor {
|
||||
onClear(event: any) {
|
||||
event.stopPropagation();
|
||||
this.onChange((this.value = null));
|
||||
this.cleared.emit();
|
||||
}
|
||||
|
||||
writeValue(value: any): void {
|
||||
|
||||
@@ -270,8 +270,8 @@
|
||||
"alert.setting.target.system_value_row_count": "Value rows",
|
||||
"alert.setting.target.tip": "The selected metric object",
|
||||
"alert.setting.template": "Alarm Content",
|
||||
"alert.setting.template.example": "Please input notice template.Eg: ${app}.${metrics}.${metric}'s value is too high",
|
||||
"alert.setting.template.label": "The notification information template sent after the alarm is triggered, see the template environment variable above",
|
||||
"alert.setting.template.example": "High CPU usage detected on instance localhost",
|
||||
"alert.setting.template.label": "The notification information template sent after the alarm is triggered",
|
||||
"alert.setting.template.metric-name": "Metric Name",
|
||||
"alert.setting.template.metric-value": "Metric Value",
|
||||
"alert.setting.template.metrics-name": "Metrics Name",
|
||||
@@ -702,7 +702,7 @@
|
||||
"monitor.privateKey.tip": "BEGIN RSA PRIVATE KEY",
|
||||
"monitor.search.app": "Type Filter",
|
||||
"monitor.search.placeholder": "Search Monitor",
|
||||
"monitor.search.tag": "Tag Filter",
|
||||
"monitor.search.label": "Label Filter",
|
||||
"monitor.sitemap.tip": "Web SITEMAP EG:/sitemap.xml",
|
||||
"monitor.spinning-tip.detecting": "Available Detecting",
|
||||
"monitor.status": "Task Status",
|
||||
|
||||
@@ -270,8 +270,8 @@
|
||||
"alert.setting.target.system_value_row_count": "指标值行数量",
|
||||
"alert.setting.target.tip": "阈值应用于的指标类型",
|
||||
"alert.setting.template": "告警内容",
|
||||
"alert.setting.template.example": "请输入阈值表达式,支持使用指标和操作符",
|
||||
"alert.setting.template.label": "告警触发后发送的通知信息模版,模版环境变量见上方",
|
||||
"alert.setting.template.example": "检测出 CPU 利用率过高",
|
||||
"alert.setting.template.label": "告警触发后发送的通知信息",
|
||||
"alert.setting.template.metric-name": "监控指标名称",
|
||||
"alert.setting.template.metric-value": "指标值",
|
||||
"alert.setting.template.metrics-name": "监控指标集合名称",
|
||||
@@ -420,6 +420,8 @@
|
||||
"common.confirm.delete-batch": "请确认是否批量删除!",
|
||||
"common.confirm.enable": "请确认是否启用!",
|
||||
"common.confirm.enable-batch": "请确认是否批量启用!",
|
||||
"common.copy": "复制到粘贴板",
|
||||
"common.copy.button": "复制",
|
||||
"common.disable": "关闭",
|
||||
"common.edit": "操作",
|
||||
"common.edit-time": "更新时间",
|
||||
@@ -702,7 +704,7 @@
|
||||
"monitor.privateKey.tip": "启动RSA私钥",
|
||||
"monitor.search.app": "类型筛选",
|
||||
"monitor.search.placeholder": "搜索监控",
|
||||
"monitor.search.tag": "标签筛选",
|
||||
"monitor.search.label": "标签筛选",
|
||||
"monitor.sitemap.tip": "网站地图 EG:/sitemap.xml",
|
||||
"monitor.spinning-tip.detecting": "测试连接可用性",
|
||||
"monitor.status": "任务状态",
|
||||
|
||||
@@ -270,8 +270,8 @@
|
||||
"alert.setting.target.system_value_row_count": "指標值行數量",
|
||||
"alert.setting.target.tip": "選中的指標對象",
|
||||
"alert.setting.template": "告警内容",
|
||||
"alert.setting.template.example": "請輸入告警的通知模版.示例: ${app}.${metrics}.${metric}'s value is too high",
|
||||
"alert.setting.template.label": "告警觸發後發送的通知信息模版,模版環境變量見上方",
|
||||
"alert.setting.template.example": "检测出 CPU 利用率过高",
|
||||
"alert.setting.template.label": "告警觸發後發送的通知信息",
|
||||
"alert.setting.template.metric-name": "監控指標名稱",
|
||||
"alert.setting.template.metric-value": "指標值",
|
||||
"alert.setting.template.metrics-name": "監控指標集合名稱",
|
||||
@@ -702,7 +702,7 @@
|
||||
"monitor.privateKey.tip": "啟動RSA私鑰",
|
||||
"monitor.search.app": "類型篩選",
|
||||
"monitor.search.placeholder": "搜尋監控",
|
||||
"monitor.search.tag": "標籤篩選",
|
||||
"monitor.search.label": "標籤篩選",
|
||||
"monitor.sitemap.tip": "網站地圖 EG:/sitemap.xml",
|
||||
"monitor.spinning-tip.detecting": "Available Detecting",
|
||||
"monitor.status": "任務狀態",
|
||||
|
||||
Reference in New Issue
Block a user