diff --git a/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/controller/MonitorController.java b/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/controller/MonitorController.java index 77b02622a91..e44ac75c4e6 100644 --- a/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/controller/MonitorController.java +++ b/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/controller/MonitorController.java @@ -17,8 +17,6 @@ package org.apache.hertzbeat.manager.controller; -import static org.apache.hertzbeat.common.constants.CommonConstants.MONITOR_NOT_EXIST_CODE; -import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; @@ -38,6 +36,10 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import static org.apache.hertzbeat.common.constants.CommonConstants.FAIL_CODE; +import static org.apache.hertzbeat.common.constants.CommonConstants.MONITOR_NOT_EXIST_CODE; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; + /** * Monitoring management API */ @@ -101,4 +103,15 @@ public ResponseEntity> detectMonitor(@Valid @RequestBody MonitorDt monitorService.detectMonitor(monitorDto.getMonitor(), monitorDto.getParams(), monitorDto.getCollector()); return ResponseEntity.ok(Message.success("Detect success.")); } + + @PostMapping("/copy/{id}") + @Operation(summary = "Copy Monitor", description = "Copy an existing monitor") + public ResponseEntity> copyMonitor(@PathVariable("id") final Long id) { + try { + monitorService.copyMonitor(id); + return ResponseEntity.ok(Message.success("Copy monitor success")); + } catch (Exception e) { + return ResponseEntity.ok(Message.fail(FAIL_CODE, "Copy monitor failed: " + e.getMessage())); + } + } } diff --git a/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/MonitorService.java b/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/MonitorService.java index f642a9a8718..ff173393faa 100644 --- a/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/MonitorService.java +++ b/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/MonitorService.java @@ -18,9 +18,6 @@ package org.apache.hertzbeat.manager.service; import jakarta.servlet.http.HttpServletResponse; -import java.util.HashSet; -import java.util.List; -import java.util.Set; import org.apache.hertzbeat.common.entity.grafana.GrafanaDashboard; import org.apache.hertzbeat.common.entity.job.Job; import org.apache.hertzbeat.common.entity.manager.Monitor; @@ -32,6 +29,10 @@ import org.springframework.data.domain.Page; import org.springframework.web.multipart.MultipartFile; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + /** * Monitoring and management services */ @@ -39,6 +40,7 @@ public interface MonitorService { /** * Monitoring Availability Probes + * * @param monitor Monitoring entity information * @param params Parameter information * @param collector collector pinned @@ -49,16 +51,17 @@ public interface MonitorService { /** * Add monitoring * - * @param monitor Monitoring Entity - * @param params Parameter information - * @param collector collector pinned - * @param dashboard grafana dashboard + * @param monitor Monitoring Entity + * @param params Parameter information + * @param collector collector pinned + * @param dashboard grafana dashboard * @throws RuntimeException Add process exception throw */ void addMonitor(Monitor monitor, List params, String collector, GrafanaDashboard dashboard) throws RuntimeException; /** * Verify the correctness of request data parameters + * * @param monitorDto monitorDto * @param isModify Whether it is a modification monitoring * @throws IllegalArgumentException Validation parameter error thrown @@ -68,16 +71,17 @@ public interface MonitorService { /** * Modify update monitoring * - * @param monitor Monitor Entity - * @param params Parameter information - * @param collector collector pinned - * @param dashboard grafana dashboard + * @param monitor Monitor Entity + * @param params Parameter information + * @param collector collector pinned + * @param dashboard grafana dashboard * @throws RuntimeException Exception thrown during modification */ void modifyMonitor(Monitor monitor, List params, String collector, GrafanaDashboard dashboard) throws RuntimeException; /** * Delete Monitor + * * @param id Monitor ID * @throws RuntimeException Exception thrown during deletion */ @@ -85,6 +89,7 @@ public interface MonitorService { /** * Batch delete monitoring + * * @param ids Monitoring ID List * @throws RuntimeException Exception thrown during deletion */ @@ -92,6 +97,7 @@ public interface MonitorService { /** * Get monitoring information + * * @param id Monitor ID * @return MonitorDto Monitor Entity * @throws RuntimeException Exception thrown during query @@ -100,39 +106,44 @@ public interface MonitorService { /** * Dynamic conditional query + * * @param monitorIds Monitor ID List - * @param app Monitor Type - * @param search Monitor Host support fuzzy query - * @param status Monitor Status 0:no monitor,1:usable,2:disabled,9:all status - * @param sort Sort Field - * @param order Sort mode eg:asc desc - * @param pageIndex List current page - * @param pageSize Number of list pagination - * @param labels Monitor labels + * @param app Monitor Type + * @param search Monitor Host support fuzzy query + * @param status Monitor Status 0:no monitor,1:usable,2:disabled,9:all status + * @param sort Sort Field + * @param order Sort mode eg:asc desc + * @param pageIndex List current page + * @param pageSize Number of list pagination + * @param labels Monitor labels * @return Search Result */ Page getMonitors(List monitorIds, String app, String search, Byte status, String sort, String order, int pageIndex, int pageSize, String labels); /** * Unmanaged monitoring items in batches according to the monitoring ID list + * * @param ids Monitoring ID List */ void cancelManageMonitors(HashSet ids); /** * Start the managed monitoring items in batches according to the monitoring ID list + * * @param ids Monitoring ID List */ void enableManageMonitors(HashSet ids); /** * Query the monitoring category and its corresponding monitoring quantity + * * @return Monitoring Category and Monitoring Quantity Mapping */ List getAllAppMonitorsCount(); /** * Query monitoring + * * @param monitorId Monitor ID * @return Monitor information */ @@ -140,6 +151,7 @@ public interface MonitorService { /** * Update the status of the specified monitor + * * @param monitorId monitorId * @param status monitor status */ @@ -147,6 +159,7 @@ public interface MonitorService { /** * Query the list of all monitoring information under the specified monitoring type + * * @param app Monitor Type * @return Monitor Entity List */ @@ -154,6 +167,7 @@ public interface MonitorService { /** * Export Monitoring Configuration + * * @param ids monitor id list * @param type file type * @param res response @@ -163,6 +177,7 @@ public interface MonitorService { /** * Import Monitoring Configuration + * * @param file configuration file * @throws Exception This exception will be thrown if the export fails */ @@ -177,9 +192,17 @@ public interface MonitorService { /** * update app collect job by app + * * @param job job content */ void updateAppCollectJob(Job job); void addAndSaveMonitorJob(Monitor monitor, List params, String collector, SdMonitorParam sdMonitorParam, GrafanaDashboard grafanaDashboard); + + /** + * Copy monitor by id + * + * @param id Monitor id + */ + void copyMonitor(Long id); } diff --git a/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/impl/MonitorServiceImpl.java b/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/impl/MonitorServiceImpl.java index fd38159e0c0..00f7c6fbfe1 100644 --- a/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/impl/MonitorServiceImpl.java +++ b/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/impl/MonitorServiceImpl.java @@ -22,20 +22,6 @@ import jakarta.persistence.criteria.CriteriaBuilder; import jakarta.persistence.criteria.Predicate; import jakarta.servlet.http.HttpServletResponse; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.apache.hertzbeat.alert.dao.AlertDefineBindDao; @@ -83,6 +69,7 @@ import org.apache.hertzbeat.manager.support.exception.MonitorDatabaseException; import org.apache.hertzbeat.manager.support.exception.MonitorDetectException; import org.apache.hertzbeat.warehouse.service.WarehouseService; +import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.data.domain.Page; @@ -96,6 +83,21 @@ import org.springframework.util.CollectionUtils; import org.springframework.web.multipart.MultipartFile; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + /** * Monitoring and management service implementation */ @@ -103,55 +105,40 @@ @Transactional(rollbackFor = Exception.class) @Slf4j public class MonitorServiceImpl implements MonitorService { - private static final Long MONITOR_ID_TMP = 1000000000L; - public static final String PATTERN_HTTP = "(?i)http://"; public static final String PATTERN_HTTPS = "(?i)https://"; - + private static final Long MONITOR_ID_TMP = 1000000000L; private static final byte ALL_MONITOR_STATUS = 9; private static final int TAG_LENGTH = 2; private static final String CONTENT_VALUE = MediaType.APPLICATION_OCTET_STREAM_VALUE + SignConstants.SINGLE_MARK + "charset=" + StandardCharsets.UTF_8; - + private final Map imExportServiceMap = new HashMap<>(); @Autowired private AppService appService; - @Autowired private TagService tagService; - @Autowired private CollectJobScheduling collectJobScheduling; - @Autowired private MonitorDao monitorDao; - @Autowired private ParamDao paramDao; - @Autowired private MonitorBindDao monitorBindDao; - @Autowired private CollectorDao collectorDao; - @Autowired private CollectorMonitorBindDao collectorMonitorBindDao; - @Autowired private AlertDefineBindDao alertDefineBindDao; - @Autowired private ApplicationContext applicationContext; - @Autowired private WarehouseService warehouseService; - @Autowired private DashboardService dashboardService; - private final Map imExportServiceMap = new HashMap<>(); - public MonitorServiceImpl(List imExportServiceList) { imExportServiceList.forEach(it -> imExportServiceMap.put(it.type(), it)); } @@ -453,7 +440,8 @@ public void modifyMonitor(Monitor monitor, List params, String collector, try { detectMonitor(monitor, params, collector); - } catch (Exception ignored) {} + } catch (Exception ignored) { + } // After the update is successfully released, refresh the database try { @@ -704,7 +692,8 @@ public void enableManageMonitors(HashSet ids) { applicationContext.publishEvent(new MonitorDeletedEvent(applicationContext, monitor.getId())); try { detectMonitor(monitor, params, collector); - } catch (Exception ignored) {} + } catch (Exception ignored) { + } } monitorDao.saveAll(unManagedMonitors); } @@ -721,10 +710,14 @@ public List getAllAppMonitorsCount() { AppCount appCount = appCountMap.getOrDefault(item.getApp(), new AppCount()); appCount.setApp(item.getApp()); switch (item.getStatus()) { - case CommonConstants.MONITOR_UP_CODE -> appCount.setAvailableSize(appCount.getAvailableSize() + item.getSize()); - case CommonConstants.MONITOR_DOWN_CODE -> appCount.setUnAvailableSize(appCount.getUnAvailableSize() + item.getSize()); - case CommonConstants.MONITOR_PAUSED_CODE -> appCount.setUnManageSize(appCount.getUnManageSize() + item.getSize()); - default -> {} + case CommonConstants.MONITOR_UP_CODE -> + appCount.setAvailableSize(appCount.getAvailableSize() + item.getSize()); + case CommonConstants.MONITOR_DOWN_CODE -> + appCount.setUnAvailableSize(appCount.getUnAvailableSize() + item.getSize()); + case CommonConstants.MONITOR_PAUSED_CODE -> + appCount.setUnManageSize(appCount.getUnManageSize() + item.getSize()); + default -> { + } } appCountMap.put(item.getApp(), appCount); } @@ -754,7 +747,7 @@ public void copyMonitors(List ids) { // deep copy original monitor to achieve persist in JPA Monitor newMonitor = JsonUtil.fromJson(JsonUtil.toJson(monitor), Monitor.class); if (newMonitor != null) { - copyMonitor(newMonitor, params); + copyMonitor(newMonitor, params); } }, () -> log.warn("can not find the monitor for id :{}", id)); }); @@ -805,7 +798,7 @@ public void updateAppCollectJob(Job job) { // Delivering a collection task long newJobId = collectJobScheduling.updateAsyncCollectJob(appDefine, collector); monitor.setJobId(newJobId); - monitorDao.save(monitor); + monitorDao.save(monitor); } catch (Exception e) { log.error("update monitor job error when template modify: {}.continue", e.getMessage(), e); } @@ -851,7 +844,8 @@ public void addAndSaveMonitorJob(Monitor monitor, List params, String col try { detectMonitor(monitor, params, collector); - } catch (Exception ignored) {} + } catch (Exception ignored) { + } try { if (collector != null) { @@ -1015,4 +1009,90 @@ private List filterTags(List tags) { .filter(tag -> !(tag.getName().equals(CommonConstants.TAG_MONITOR_ID) || tag.getName().equals(CommonConstants.TAG_MONITOR_NAME))) .collect(Collectors.toList()); } + + @Override + @Transactional(rollbackFor = Exception.class) + public void copyMonitor(Long id) { + // Get the source monitor information + Optional monitorOptional = monitorDao.findById(id); + if (monitorOptional.isEmpty()) { + throw new IllegalArgumentException("Monitor not found: " + id); + } + Monitor sourceMonitor = monitorOptional.get(); + + // Get the parameters of source monitor + List sourceParams = paramDao.findParamsByMonitorId(id); + + // Create new monitor object + Monitor newMonitor = new Monitor(); + // Copy basic properties, exclude ID, jobId and status + BeanUtils.copyProperties(sourceMonitor, newMonitor, "id", "jobId", "status"); + // Set new name + newMonitor.setName(sourceMonitor.getName() + "_copy"); + // Set initial status + newMonitor.setStatus(CommonConstants.MONITOR_UP_CODE); + // Set create and update time + newMonitor.setGmtCreate(LocalDateTime.now()); + newMonitor.setGmtUpdate(LocalDateTime.now()); + // Generate new ID using snowflake algorithm + newMonitor.setId(SnowFlakeIdGenerator.generateId()); + // Save new monitor + newMonitor = monitorDao.save(newMonitor); + + // Ensure ID is set + if (newMonitor.getId() == null) { + throw new RuntimeException("Failed to generate monitor ID"); + } + + // Copy parameters + if (!sourceParams.isEmpty()) { + List newParams = new ArrayList<>(); + for (Param sourceParam : sourceParams) { + Param newParam = new Param(); + BeanUtils.copyProperties(sourceParam, newParam, "id"); + newParam.setMonitorId(newMonitor.getId()); + newParams.add(newParam); + } + paramDao.saveAll(newParams); + } + + try { + // Build collect job + Job appDefine = appService.getAppDefine(newMonitor.getApp()); + if (CommonConstants.PROMETHEUS.equals(newMonitor.getApp())) { + appDefine.setApp(CommonConstants.PROMETHEUS_APP_PREFIX + newMonitor.getName()); + } + // Ensure using correct monitor ID + appDefine.setMonitorId(newMonitor.getId()); + appDefine.setDefaultInterval(newMonitor.getIntervals()); + appDefine.setCyclic(true); + appDefine.setTimestamp(System.currentTimeMillis()); + List configmaps = sourceParams.stream() + .map(param -> new Configmap(param.getField(), param.getParamValue(), param.getType())) + .collect(Collectors.toList()); + appDefine.setConfigmap(configmaps); + + // Get collector configuration from source monitor + Optional bindOptional = + collectorMonitorBindDao.findCollectorMonitorBindByMonitorId(sourceMonitor.getId()); + String collector = bindOptional.map(CollectorMonitorBind::getCollector).orElse(null); + + // Dispatch collect job + long jobId = collectJobScheduling.addAsyncCollectJob(appDefine, collector); + newMonitor.setJobId(jobId); + monitorDao.save(newMonitor); + + // Copy collector binding if exists + if (collector != null) { + CollectorMonitorBind newBind = CollectorMonitorBind.builder() + .collector(collector) + .monitorId(newMonitor.getId()) + .build(); + collectorMonitorBindDao.save(newBind); + } + } catch (Exception e) { + log.error("Create collect job error: {}", e.getMessage(), e); + throw new RuntimeException("Create collect job failed: " + e.getMessage()); + } + } } diff --git a/web-app/src/app/routes/monitor/monitor-list/monitor-list.component.html b/web-app/src/app/routes/monitor/monitor-list/monitor-list.component.html index 65cb0e02b15..bb9920dd277 100644 --- a/web-app/src/app/routes/monitor/monitor-list/monitor-list.component.html +++ b/web-app/src/app/routes/monitor/monitor-list/monitor-list.component.html @@ -85,6 +85,12 @@ +
  • + +
  • diff --git a/web-app/src/app/routes/monitor/monitor-list/monitor-list.component.ts b/web-app/src/app/routes/monitor/monitor-list/monitor-list.component.ts index 7d921d6603e..04dca1effaa 100644 --- a/web-app/src/app/routes/monitor/monitor-list/monitor-list.component.ts +++ b/web-app/src/app/routes/monitor/monitor-list/monitor-list.component.ts @@ -603,4 +603,30 @@ export class MonitorListComponent implements OnInit, OnDestroy { } return hash; } + + copyMonitor() { + if (this.checkedMonitorIds == null || this.checkedMonitorIds.size === 0) { + this.notifySvc.warning(this.i18nSvc.fanyi('common.notify.no-select-delete'), ''); + return; + } + if (this.checkedMonitorIds.size > 1) { + this.notifySvc.warning(this.i18nSvc.fanyi('monitors.copy.notify.one-select'), ''); + return; + } + const monitorId = Array.from(this.checkedMonitorIds)[0]; + + this.monitorSvc.copyMonitor(monitorId).subscribe( + message => { + if (message.code === 0) { + this.notifySvc.success(this.i18nSvc.fanyi('monitors.copy.success'), ''); + this.loadMonitorTable(); + } else { + this.notifySvc.error(this.i18nSvc.fanyi('monitors.copy.failed'), message.msg); + } + }, + error => { + this.notifySvc.error(this.i18nSvc.fanyi('monitors.copy.failed'), error.msg); + } + ); + } } diff --git a/web-app/src/app/service/monitor.service.ts b/web-app/src/app/service/monitor.service.ts index 571bc4bfaad..eb576c75dd5 100644 --- a/web-app/src/app/service/monitor.service.ts +++ b/web-app/src/app/service/monitor.service.ts @@ -166,7 +166,7 @@ export class MonitorService { interval: interval }); const options = { params: httpParams }; - return this.http.get>(`/monitor/${monitorId}/metric/${metricFull}`, options); + return this.http.get>(`${monitor_uri}/${monitorId}/metric/${metricFull}`, options); } public getAppsMonitorSummary(): Observable> { @@ -184,4 +184,8 @@ export class MonitorService { public deleteGrafanaDashboard(monitorId: number): Observable> { return this.http.delete>(`${grafana_dashboard_uri}?monitorId=${monitorId}`); } + + copyMonitor(id: number): Observable { + return this.http.post>(`${monitor_uri}/copy/${id}`, null); + } } diff --git a/web-app/src/assets/i18n/en-US.json b/web-app/src/assets/i18n/en-US.json index c7d72104522..6b6c265f279 100644 --- a/web-app/src/assets/i18n/en-US.json +++ b/web-app/src/assets/i18n/en-US.json @@ -851,5 +851,10 @@ "annotation": "Annotation", "annotation.bind": "Bind Annotation", "annotation.bind.tip": "You can use annotations to mark entity information, such as binding annotations of important events to resources.", - "label": "Label" + "label": "Label", + "monitors.copy": "Copy Monitor", + "monitors.copy-monitor": "Copy Monitor", + "monitors.copy.success": "Copy Monitor Success", + "monitors.copy.failed": "Copy Monitor Failed", + "monitors.copy.notify.one-select": "Only one monitor can be selected to copy" } diff --git a/web-app/src/assets/i18n/zh-CN.json b/web-app/src/assets/i18n/zh-CN.json index 968c73f62a1..e0a88e9e55d 100644 --- a/web-app/src/assets/i18n/zh-CN.json +++ b/web-app/src/assets/i18n/zh-CN.json @@ -859,5 +859,10 @@ "alert.inhibit.delete": "删除抑制规则", "alert.help.inhibit": "告警抑制用于配置告警之间的抑制关系。当某个告警发生时,可以抑制其他告警的产生。例如,当服务器宕机时,可以抑制该服务器上的所有告警。", "alert.help.inhibit.link": "https://hertzbeat.apache.org/zh-cn/docs/help/alert_inhibit", - "alert.center.tag.search.placeholder": "搜索标签..." + "alert.center.tag.search.placeholder": "搜索标签...", + "monitors.copy": "复制监控", + "monitors.copy-monitor": "复制监控", + "monitors.copy.success": "复制监控成功", + "monitors.copy.failed": "复制监控失败", + "monitors.copy.notify.one-select": "只能选择一个监控进行复制" }