diff --git a/common/src/main/java/org/apache/hertzbeat/common/entity/manager/Monitor.java b/common/src/main/java/org/apache/hertzbeat/common/entity/manager/Monitor.java index dd59207da41..2d3665bf13a 100644 --- a/common/src/main/java/org/apache/hertzbeat/common/entity/manager/Monitor.java +++ b/common/src/main/java/org/apache/hertzbeat/common/entity/manager/Monitor.java @@ -139,14 +139,14 @@ public class Monitor { /** * Record create time */ - @Schema(title = "Record create time", example = "1612198922000", accessMode = READ_ONLY) + @Schema(title = "Record create time", example = "2024-07-02T20:09:34.903217", accessMode = READ_ONLY) @CreatedDate private LocalDateTime gmtCreate; /** * Record the latest modification time (timestamp in milliseconds) */ - @Schema(title = "Record modify time", example = "1612198444000", accessMode = READ_ONLY) + @Schema(title = "Record modify time", example = "2024-07-02T20:09:34.903217", accessMode = READ_ONLY) @LastModifiedDate private LocalDateTime gmtUpdate; diff --git a/common/src/main/java/org/apache/hertzbeat/common/entity/manager/bulletin/Bulletin.java b/common/src/main/java/org/apache/hertzbeat/common/entity/manager/bulletin/Bulletin.java new file mode 100644 index 00000000000..a500a6320f4 --- /dev/null +++ b/common/src/main/java/org/apache/hertzbeat/common/entity/manager/bulletin/Bulletin.java @@ -0,0 +1,97 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.hertzbeat.common.entity.manager.bulletin; + +import static io.swagger.v3.oas.annotations.media.Schema.AccessMode.READ_WRITE; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.persistence.Column; +import jakarta.persistence.Convert; +import jakarta.persistence.Entity; +import jakarta.persistence.EntityListeners; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import java.time.LocalDateTime; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.apache.hertzbeat.common.entity.manager.JsonLongListAttributeConverter; +import org.apache.hertzbeat.common.entity.manager.JsonTagListAttributeConverter; +import org.apache.hertzbeat.common.entity.manager.TagItem; +import org.springframework.data.annotation.CreatedBy; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedBy; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +/** + * Bulletin + */ +@Entity +@Data +@Schema(description = "Bulletin") +@Builder +@AllArgsConstructor +@NoArgsConstructor +@EntityListeners(AuditingEntityListener.class) +@Table(name = "hzb_bulletin") +public class Bulletin { + + @Id + @Schema(description = "Bulletin ID", example = "1") + private Long id; + + @Schema(description = "Bulletin Name", example = "Bulletin1", accessMode = READ_WRITE) + private String name; + + @Schema(description = "Monitor IDs", example = "1") + @Column(name = "monitor_ids", length = 5000) + @Convert(converter = JsonLongListAttributeConverter.class) + private List monitorIds; + + @Schema(description = "Monitor Type eg: jvm, tomcat", example = "jvm", accessMode = READ_WRITE) + private String app; + + + @Schema(description = "Monitor Fields") + @Column(length = 4096, columnDefinition = "json") + private String fields; + + @Schema(description = "Tags(status:success,env:prod)", example = "{name: key1, value: value1}", + accessMode = READ_WRITE) + @Convert(converter = JsonTagListAttributeConverter.class) + @Column(length = 2048) + private List tags; + + @Schema(title = "The creator of this record", example = "tom", accessMode = READ_WRITE) + @CreatedBy + private String creator; + + @Schema(title = "The modifier of this record", example = "tom", accessMode = READ_WRITE) + @LastModifiedBy + private String modifier; + + @Schema(title = "Record create time", example = "2024-07-02T20:09:34.903217", accessMode = READ_WRITE) + @CreatedDate + private LocalDateTime gmtCreate; + + @Schema(title = "Record modify time", example = "2024-07-02T20:09:34.903217", accessMode = READ_WRITE) + @LastModifiedDate + private LocalDateTime gmtUpdate; +} diff --git a/common/src/main/java/org/apache/hertzbeat/common/entity/manager/bulletin/BulletinDto.java b/common/src/main/java/org/apache/hertzbeat/common/entity/manager/bulletin/BulletinDto.java new file mode 100644 index 00000000000..f077fd72651 --- /dev/null +++ b/common/src/main/java/org/apache/hertzbeat/common/entity/manager/bulletin/BulletinDto.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package org.apache.hertzbeat.common.entity.manager.bulletin; + +import java.util.List; +import java.util.Map; +import lombok.Data; + +/** + * Bulletin DTO + */ +@Data +public class BulletinDto { + + /** + * Bulletin name + */ + private String name; + + /** + * Monitor type eg: jvm, tomcat + */ + private String app; + + + /** + * Monitor fields + */ + private Map> fields; + + /** + * Monitor ids + */ + private List monitorIds; + +} diff --git a/common/src/main/java/org/apache/hertzbeat/common/entity/manager/bulletin/BulletinMetricsData.java b/common/src/main/java/org/apache/hertzbeat/common/entity/manager/bulletin/BulletinMetricsData.java new file mode 100644 index 00000000000..33f93a20048 --- /dev/null +++ b/common/src/main/java/org/apache/hertzbeat/common/entity/manager/bulletin/BulletinMetricsData.java @@ -0,0 +1,135 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.hertzbeat.common.entity.manager.bulletin; + +import io.swagger.v3.oas.annotations.media.Schema; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * Bulletin Metrics Data + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Schema(description = "Bulletin Metrics Data") +public class BulletinMetricsData { + + /** + * Bulletin Name + */ + @Schema(title = "Bulletin Name") + private String name; + + /** + * Content Data + */ + @Schema(description = "Content Data") + private List content; + + /** + * Bulletin Metrics Data + */ + @lombok.Data + @Builder + @AllArgsConstructor + @NoArgsConstructor + public static class Data { + + /** + * Monitor Name + */ + @Schema(title = "Monitor name") + private String monitorName; + + /** + * Monitor ID + */ + @Schema(title = "Monitor ID") + private Long monitorId; + + /** + * Monitor IP + */ + @Schema(title = "Monitor IP") + private String host; + + /** + * Monitor Metrics + */ + @Schema(title = "Monitor Metrics") + private List metrics; + } + + /** + * Metrics Data + */ + @lombok.Data + @Builder + @AllArgsConstructor + @NoArgsConstructor + @Schema(description = "Metrics Data") + public static class Metric{ + + /** + * Metric type + */ + @Schema(title = "Metric type") + private String name; + + /** + * Metric fields + */ + @Schema(title = "Metric fields") + private List> fields; + } + + + /** + * Metrics field + */ + @lombok.Data + @Builder + @AllArgsConstructor + @NoArgsConstructor + @Schema(description = "Metrics field") + public static class Field{ + + /** + * Field name + */ + @Schema(title = "Field name") + private String key; + + /** + * Field unit + */ + @Schema(title = "Field unit") + private String unit; + + /** + * Field value + */ + @Schema(title = "Field value") + private String value; + } +} diff --git a/common/src/main/java/org/apache/hertzbeat/common/entity/manager/bulletin/BulletinVo.java b/common/src/main/java/org/apache/hertzbeat/common/entity/manager/bulletin/BulletinVo.java new file mode 100644 index 00000000000..a49fbb18a51 --- /dev/null +++ b/common/src/main/java/org/apache/hertzbeat/common/entity/manager/bulletin/BulletinVo.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.hertzbeat.common.entity.manager.bulletin; + +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.apache.hertzbeat.common.entity.manager.TagItem; + +/** + * Bulletin Vo + */ + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class BulletinVo { + + /** + * Bulletin ID + */ + private Long id; + + /** + * Bulletin name + */ + private String name; + + /** + * Bulletin metrics + */ + private List metrics; + + /** + * Bulletin tags + */ + private List tags; + + /** + * Bulletin monitor ID + */ + private List monitorId; + + /** + * Bulletin monitor name + */ + private String app; + +} diff --git a/common/src/main/java/org/apache/hertzbeat/common/util/JsonUtil.java b/common/src/main/java/org/apache/hertzbeat/common/util/JsonUtil.java index 7e8fbed5243..b95619bb229 100644 --- a/common/src/main/java/org/apache/hertzbeat/common/util/JsonUtil.java +++ b/common/src/main/java/org/apache/hertzbeat/common/util/JsonUtil.java @@ -100,11 +100,15 @@ public static JsonNode fromJson(String jsonStr) { * @param jsonStr json string * @return true if the string is a json string */ + + public static boolean isJsonStr(String jsonStr) { - if (!StringUtils.hasText(jsonStr)) { + if (jsonStr == null || jsonStr.trim().isEmpty()) { return false; } - if (!jsonStr.startsWith("{") || !jsonStr.endsWith("}")) { + jsonStr = jsonStr.trim(); + if (!(jsonStr.startsWith("{") && jsonStr.endsWith("}")) + && !(jsonStr.startsWith("[") && jsonStr.endsWith("]"))) { return false; } try { diff --git a/common/src/test/java/org/apache/hertzbeat/common/util/JsonUtilTest.java b/common/src/test/java/org/apache/hertzbeat/common/util/JsonUtilTest.java index 628a10e105b..d4acb6089ed 100644 --- a/common/src/test/java/org/apache/hertzbeat/common/util/JsonUtilTest.java +++ b/common/src/test/java/org/apache/hertzbeat/common/util/JsonUtilTest.java @@ -20,6 +20,7 @@ import static org.apache.hertzbeat.common.util.JsonUtil.isJsonStr; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; import com.fasterxml.jackson.core.type.TypeReference; import java.util.ArrayList; import java.util.List; @@ -66,7 +67,7 @@ void testIsJsonStr() { assertFalse(isJsonStr(jsonString)); String jsonStringArrays = "[{\"name\":\"John\"}, {\"name\":\"Doe\"}]"; - assertFalse(isJsonStr(jsonStringArrays)); + assertTrue(isJsonStr(jsonStringArrays)); } } diff --git a/manager/src/main/java/org/apache/hertzbeat/manager/controller/AppController.java b/manager/src/main/java/org/apache/hertzbeat/manager/controller/AppController.java index d3d98a95546..5472f0f39e2 100644 --- a/manager/src/main/java/org/apache/hertzbeat/manager/controller/AppController.java +++ b/manager/src/main/java/org/apache/hertzbeat/manager/controller/AppController.java @@ -25,6 +25,7 @@ import jakarta.validation.Valid; import java.util.List; import java.util.Locale; +import java.util.Map; import org.apache.hertzbeat.common.entity.dto.Message; import org.apache.hertzbeat.common.entity.job.Job; import org.apache.hertzbeat.common.entity.manager.ParamDefine; @@ -155,6 +156,35 @@ public ResponseEntity>> queryAppsHierarchy( @Parameter(description = "en: language type", example = "zh-CN") @RequestParam(name = "lang", required = false) String lang) { + lang = getLang(lang); + List appHierarchies = appService.getAllAppHierarchy(lang); + return ResponseEntity.ok(Message.success(appHierarchies)); + } + + @GetMapping(path = "/hierarchy/{app}") + @Operation(summary = "Query all monitor metrics level, output in a hierarchical structure", description = "Query all monitor metrics level, output in a hierarchical structure") + public ResponseEntity>> queryAppsHierarchyByApp( + @Parameter(description = "en: language type", + example = "zh-CN") + @RequestParam(name = "lang", required = false) String lang, + @Parameter(description = "en: Monitoring type name", example = "api") @PathVariable("app") final String app) { + lang = getLang(lang); + List appHierarchies = appService.getAppHierarchy(app, lang); + return ResponseEntity.ok(Message.success(appHierarchies)); + } + + @GetMapping(path = "/defines") + @Operation(summary = "Query all monitor types", description = "Query all monitor types") + public ResponseEntity>> getAllAppDefines( + @Parameter(description = "en: language type", + example = "zh-CN") + @RequestParam(name = "lang", required = false) String lang) { + lang = getLang(lang); + Map allAppDefines = appService.getI18nApps(lang); + return ResponseEntity.ok(Message.success(allAppDefines)); + } + + private String getLang(@RequestParam(name = "lang", required = false) @Parameter(description = "en: language type", example = "zh-CN") String lang) { if (lang == null || lang.isEmpty()) { lang = "zh-CN"; } @@ -165,7 +195,6 @@ public ResponseEntity>> queryAppsHierarchy( } else { lang = "en-US"; } - List appHierarchies = appService.getAllAppHierarchy(lang); - return ResponseEntity.ok(Message.success(appHierarchies)); + return lang; } } diff --git a/manager/src/main/java/org/apache/hertzbeat/manager/controller/BulletinController.java b/manager/src/main/java/org/apache/hertzbeat/manager/controller/BulletinController.java new file mode 100644 index 00000000000..60490597647 --- /dev/null +++ b/manager/src/main/java/org/apache/hertzbeat/manager/controller/BulletinController.java @@ -0,0 +1,148 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package org.apache.hertzbeat.manager.controller; + +import static org.apache.hertzbeat.common.constants.CommonConstants.FAIL_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 jakarta.validation.Valid; +import java.util.List; +import lombok.extern.slf4j.Slf4j; +import org.apache.hertzbeat.common.entity.dto.Message; +import org.apache.hertzbeat.common.entity.manager.bulletin.Bulletin; +import org.apache.hertzbeat.common.entity.manager.bulletin.BulletinDto; +import org.apache.hertzbeat.common.entity.manager.bulletin.BulletinMetricsData; +import org.apache.hertzbeat.manager.service.BulletinService; +import org.apache.hertzbeat.manager.service.MonitorService; +import org.apache.hertzbeat.warehouse.store.realtime.RealTimeDataReader; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +/** + * Bulletin Controller + */ +@Slf4j +@RestController +@RequestMapping(value = "/api/bulletin", produces = {APPLICATION_JSON_VALUE}) +public class BulletinController { + + @Autowired + private BulletinService bulletinService; + + @Autowired + private RealTimeDataReader realTimeDataReader; + + @Autowired + private MonitorService monitorService; + + /** + * add a new bulletin + */ + @PostMapping + public ResponseEntity> addNewBulletin(@Valid @RequestBody BulletinDto bulletinDto) { + try { + bulletinService.validate(bulletinDto); + bulletinService.addBulletin(bulletinDto); + } catch (Exception e) { + return ResponseEntity.ok(Message.fail(FAIL_CODE, "Add failed! " + e.getMessage())); + } + return ResponseEntity.ok(Message.success("Add success!")); + } + + /** + * edit a exist bulletin + */ + @PutMapping + public ResponseEntity> editBulletin(@Valid @RequestBody BulletinDto bulletinDto) { + try { + bulletinService.validate(bulletinDto); + bulletinService.editBulletin(bulletinDto); + } catch (Exception e) { + return ResponseEntity.ok(Message.fail(FAIL_CODE, "Add failed! " + e.getMessage())); + } + return ResponseEntity.ok(Message.success("Add success!")); + } + + /** + * edit a exist bulletin + */ + @GetMapping("/{name}") + public ResponseEntity> getBulletinByName(@Valid @PathVariable String name) { + try { + return ResponseEntity.ok(Message.success(bulletinService.getBulletinByName(name))); + } catch (Exception e) { + return ResponseEntity.ok(Message.fail(FAIL_CODE, "Add failed! " + e.getMessage())); + } + } + + /** + * get All Names + */ + @Operation(summary = "Get All Bulletin Names", description = "Get All Bulletin Names") + @GetMapping("/names") + public ResponseEntity>> getAllNames() { + List names = bulletinService.getAllNames(); + return ResponseEntity.ok(Message.success(names)); + } + + /** + * delete bulletin by name + */ + @Operation(summary = "Delete Bulletin by Name", description = "Delete Bulletin by Name") + @DeleteMapping + public ResponseEntity> deleteBulletin( + @Parameter(description = "Bulletin Name", example = "402372614668544") + @RequestParam List names) { + try { + bulletinService.deleteBulletinByName(names); + } catch (Exception e) { + return ResponseEntity.ok(Message.fail(FAIL_CODE, "Delete failed!" + e.getMessage())); + } + return ResponseEntity.ok(Message.success("Delete success!")); + } + + @GetMapping("/metrics") + @Operation(summary = "Query All Bulletin Real Time Metrics Data", description = "Query All Bulletin real-time metrics data of monitoring indicators") + public ResponseEntity> getAllMetricsData( + @RequestParam(name = "name") String name, + @RequestParam(defaultValue = "0", name = "pageIndex") int pageIndex, + @RequestParam(defaultValue = "10", name = "pageSize") int pageSize) { + if (!realTimeDataReader.isServerAvailable()) { + return ResponseEntity.ok(Message.fail(FAIL_CODE, "real time store not available")); + } + + Bulletin bulletin = bulletinService.getBulletinByName(name); + + BulletinMetricsData.BulletinMetricsDataBuilder contentBuilder = BulletinMetricsData.builder() + .name(bulletin.getName()); + BulletinMetricsData data = bulletinService.buildBulletinMetricsData(contentBuilder, bulletin); + return ResponseEntity.ok(Message.success(data)); + } + +} diff --git a/manager/src/main/java/org/apache/hertzbeat/manager/dao/BulletinDao.java b/manager/src/main/java/org/apache/hertzbeat/manager/dao/BulletinDao.java new file mode 100644 index 00000000000..0be6a8320a5 --- /dev/null +++ b/manager/src/main/java/org/apache/hertzbeat/manager/dao/BulletinDao.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.hertzbeat.manager.dao; + + +import java.util.List; +import org.apache.hertzbeat.common.entity.manager.bulletin.Bulletin; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; + +/** + * BulletinDao + */ +public interface BulletinDao extends JpaRepository, JpaSpecificationExecutor { + /** + * Delete Bulletin by name + */ + void deleteByNameIn(List names); + + /** + * Get Bulletin by name + */ + Bulletin findByName(String name); + + +} diff --git a/manager/src/main/java/org/apache/hertzbeat/manager/service/AppService.java b/manager/src/main/java/org/apache/hertzbeat/manager/service/AppService.java index d34df783cae..21144afb645 100644 --- a/manager/src/main/java/org/apache/hertzbeat/manager/service/AppService.java +++ b/manager/src/main/java/org/apache/hertzbeat/manager/service/AppService.java @@ -80,6 +80,14 @@ public interface AppService { */ Map getI18nResources(String lang); + /** + * Get the I 18 N resources of the monitoring type + * + * @param lang Language type + * @return I18N Resources + */ + Map getI18nApps(String lang); + /** * Query all types of monitoring hierarchy * @@ -88,6 +96,15 @@ public interface AppService { */ List getAllAppHierarchy(String lang); + /** + * Get the monitoring hierarchy based on the monitoring type + * + * @param app monitoring type + * @param lang language + * @return hierarchy information + */ + List getAppHierarchy(String app, String lang); + /** * Get all app define * diff --git a/manager/src/main/java/org/apache/hertzbeat/manager/service/BulletinService.java b/manager/src/main/java/org/apache/hertzbeat/manager/service/BulletinService.java new file mode 100644 index 00000000000..b7022fb29ec --- /dev/null +++ b/manager/src/main/java/org/apache/hertzbeat/manager/service/BulletinService.java @@ -0,0 +1,84 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.hertzbeat.manager.service; + +import java.util.List; +import java.util.Optional; +import org.apache.hertzbeat.common.entity.manager.bulletin.Bulletin; +import org.apache.hertzbeat.common.entity.manager.bulletin.BulletinDto; +import org.apache.hertzbeat.common.entity.manager.bulletin.BulletinMetricsData; +import org.apache.hertzbeat.common.entity.manager.bulletin.BulletinVo; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.jpa.domain.Specification; + +/** + * Bulletin Service + */ +public interface BulletinService { + + /** + * validate Bulletin + */ + void validate(BulletinDto bulletindto) throws IllegalArgumentException; + + /** + * Get Bulletin by name + */ + Bulletin getBulletinByName(String name); + + /** + * Get Bulletin by id + */ + Optional getBulletinById(Long id); + + /** + * Get all names + */ + List getAllNames(); + + + /** + * delete Bulletin by id + */ + void deleteBulletinByName(List names); + + + /** + * Save Bulletin + */ + void editBulletin(BulletinDto bulletinDto); + + /** + * Add Bulletin + */ + void addBulletin(BulletinDto bulletinDto); + + /** + * Dynamic conditional query + * @param specification Query conditions + * @param pageRequest Paging parameters + * @return The query results + */ + Page getBulletins(Specification specification, PageRequest pageRequest); + + /** + * deal with the bulletin + */ + BulletinMetricsData buildBulletinMetricsData(BulletinMetricsData.BulletinMetricsDataBuilder contentBuilder, Bulletin bulletin); +} diff --git a/manager/src/main/java/org/apache/hertzbeat/manager/service/impl/AppServiceImpl.java b/manager/src/main/java/org/apache/hertzbeat/manager/service/impl/AppServiceImpl.java index 713172b9c1e..a3a2b911373 100644 --- a/manager/src/main/java/org/apache/hertzbeat/manager/service/impl/AppServiceImpl.java +++ b/manager/src/main/java/org/apache/hertzbeat/manager/service/impl/AppServiceImpl.java @@ -255,6 +255,20 @@ public Map getI18nResources(String lang) { return i18nMap; } + @Override + public Map getI18nApps(String lang) { + Map i18nMap = new HashMap<>(128); + for (var job : appDefines.values()) { + var name = job.getName(); + var i18nName = CommonUtil.getLangMappingValueFromI18nMap(lang, name); + if (i18nName != null) { + i18nMap.put(job.getApp(), i18nName); + } + } + return i18nMap; + } + + @Override public List getAllAppHierarchy(String lang) { LinkedList hierarchies = new LinkedList<>(); @@ -263,82 +277,104 @@ public List getAllAppHierarchy(String lang) { if (DispatchConstants.PROTOCOL_PUSH.equalsIgnoreCase(job.getApp())) { continue; } - var hierarchyApp = new Hierarchy(); - hierarchyApp.setCategory(job.getCategory()); - hierarchyApp.setValue(job.getApp()); - hierarchyApp.setHide(job.isHide()); - var nameMap = job.getName(); - if (nameMap != null && !nameMap.isEmpty()) { - var i18nName = CommonUtil.getLangMappingValueFromI18nMap(lang, nameMap); - if (i18nName != null) { - hierarchyApp.setLabel(i18nName); - } - } - List hierarchyMetricList = new LinkedList<>(); - if (DispatchConstants.PROTOCOL_PROMETHEUS.equalsIgnoreCase(job.getApp())) { - List monitors = monitorDao.findMonitorsByAppEquals(job.getApp()); - for (Monitor monitor : monitors) { - List metricsDataList = warehouseService.queryMonitorMetricsData(monitor.getId()); - for (CollectRep.MetricsData metricsData : metricsDataList) { - var hierarchyMetric = new Hierarchy(); - hierarchyMetric.setValue(metricsData.getMetrics()); - hierarchyMetric.setLabel(metricsData.getMetrics()); - List hierarchyFieldList = metricsData.getFieldsList().stream() - .map(item -> { - var hierarchyField = new Hierarchy(); - hierarchyField.setValue(item.getName()); - hierarchyField.setLabel(item.getName()); - hierarchyField.setIsLeaf(true); - hierarchyField.setType((byte) item.getType()); - hierarchyField.setUnit(item.getUnit()); - return hierarchyField; - }).collect(Collectors.toList()); - hierarchyMetric.setChildren(hierarchyFieldList); - // combine Hierarchy Metrics - combineHierarchyMetrics(hierarchyMetricList, hierarchyMetric); - } - } - hierarchyApp.setChildren(hierarchyMetricList); - hierarchies.addFirst(hierarchyApp); - } else { - if (job.getMetrics() != null) { - for (var metrics : job.getMetrics()) { - var hierarchyMetric = new Hierarchy(); - hierarchyMetric.setValue(metrics.getName()); - var metricsI18nName = CommonUtil.getLangMappingValueFromI18nMap(lang, metrics.getI18n()); - hierarchyMetric.setLabel(metricsI18nName != null ? metricsI18nName : metrics.getName()); - List hierarchyFieldList = new LinkedList<>(); - if (metrics.getFields() != null) { - for (var field : metrics.getFields()) { + queryAppHierarchy(lang, hierarchies, job); + } + return hierarchies; + } + + @Override + public List getAppHierarchy(String app, String lang) { + LinkedList hierarchies = new LinkedList<>(); + Job job = appDefines.get(app.toLowerCase()); + // TODO temporarily filter out push to solve the front-end problem, and open it after the subsequent design optimization + if (DispatchConstants.PROTOCOL_PUSH.equalsIgnoreCase(job.getApp())) { + return hierarchies; + } + queryAppHierarchy(lang, hierarchies, job); + return hierarchies; + } + + private void queryAppHierarchy(String lang, LinkedList hierarchies, Job job) { + var hierarchyApp = new Hierarchy(); + hierarchyApp.setCategory(job.getCategory()); + hierarchyApp.setValue(job.getApp()); + hierarchyApp.setHide(job.isHide()); + var nameMap = job.getName(); + if (nameMap != null && !nameMap.isEmpty()) { + var i18nName = CommonUtil.getLangMappingValueFromI18nMap(lang, nameMap); + if (i18nName != null) { + hierarchyApp.setLabel(i18nName); + } + } + List hierarchyMetricList = new LinkedList<>(); + if (DispatchConstants.PROTOCOL_PROMETHEUS.equalsIgnoreCase(job.getApp())) { + List monitors = monitorDao.findMonitorsByAppEquals(job.getApp()); + for (Monitor monitor : monitors) { + List metricsDataList = warehouseService.queryMonitorMetricsData(monitor.getId()); + for (CollectRep.MetricsData metricsData : metricsDataList) { + var hierarchyMetric = new Hierarchy(); + hierarchyMetric.setValue(metricsData.getMetrics()); + hierarchyMetric.setLabel(metricsData.getMetrics()); + List hierarchyFieldList = metricsData.getFieldsList().stream() + .map(item -> { var hierarchyField = new Hierarchy(); - hierarchyField.setValue(field.getField()); - var metricI18nName = CommonUtil.getLangMappingValueFromI18nMap(lang, field.getI18n()); - hierarchyField.setLabel(metricI18nName != null ? metricI18nName : field.getField()); + hierarchyField.setValue(item.getName()); + hierarchyField.setLabel(item.getName()); hierarchyField.setIsLeaf(true); - // for metric - hierarchyField.setType(field.getType()); - hierarchyField.setUnit(field.getUnit()); - hierarchyFieldList.add(hierarchyField); - } - hierarchyMetric.setChildren(hierarchyFieldList); + hierarchyField.setType((byte) item.getType()); + hierarchyField.setUnit(item.getUnit()); + return hierarchyField; + }).collect(Collectors.toList()); + hierarchyMetric.setChildren(hierarchyFieldList); + // combine Hierarchy Metrics + combineHierarchyMetrics(hierarchyMetricList, hierarchyMetric); + } + } + hierarchyApp.setChildren(hierarchyMetricList); + hierarchies.addFirst(hierarchyApp); + } else { + if (job.getMetrics() != null) { + for (var metrics : job.getMetrics()) { + var hierarchyMetric = new Hierarchy(); + hierarchyMetric.setValue(metrics.getName()); + var metricsI18nName = CommonUtil.getLangMappingValueFromI18nMap(lang, metrics.getI18n()); + hierarchyMetric.setLabel(metricsI18nName != null ? metricsI18nName : metrics.getName()); + List hierarchyFieldList = new LinkedList<>(); + if (metrics.getFields() != null) { + for (var field : metrics.getFields()) { + var hierarchyField = new Hierarchy(); + hierarchyField.setValue(field.getField()); + var metricI18nName = CommonUtil.getLangMappingValueFromI18nMap(lang, field.getI18n()); + hierarchyField.setLabel(metricI18nName != null ? metricI18nName : field.getField()); + hierarchyField.setIsLeaf(true); + // for metric + hierarchyField.setType(field.getType()); + hierarchyField.setUnit(field.getUnit()); + hierarchyFieldList.add(hierarchyField); } - hierarchyMetricList.add(hierarchyMetric); + hierarchyMetric.setChildren(hierarchyFieldList); } + hierarchyMetricList.add(hierarchyMetric); } - hierarchyApp.setChildren(hierarchyMetricList); - hierarchies.add(hierarchyApp); } + hierarchyApp.setChildren(hierarchyMetricList); + hierarchies.add(hierarchyApp); } - return hierarchies; } + private void combineHierarchyMetrics(List hierarchyMetricList, Hierarchy hierarchyMetric) { Optional preHierarchyOptional = hierarchyMetricList.stream() - .filter(item -> item.getValue().equals(hierarchyMetric.getValue())).findFirst(); + .filter(item -> item.getValue().equals(hierarchyMetric.getValue())) + .findFirst(); + if (preHierarchyOptional.isPresent()) { Hierarchy preHierarchy = preHierarchyOptional.get(); List children = preHierarchy.getChildren(); - Set childrenKey = children.stream().map(Hierarchy::getValue).collect(Collectors.toSet()); + Set childrenKey = children.stream() + .map(Hierarchy::getValue) + .collect(Collectors.toSet()); + for (Hierarchy child : hierarchyMetric.getChildren()) { if (!childrenKey.contains(child.getValue())) { children.add(child); @@ -349,6 +385,7 @@ private void combineHierarchyMetrics(List hierarchyMetricList, Hierar } } + @Override public Map getAllAppDefines() { return appDefines; diff --git a/manager/src/main/java/org/apache/hertzbeat/manager/service/impl/BulletinServiceImpl.java b/manager/src/main/java/org/apache/hertzbeat/manager/service/impl/BulletinServiceImpl.java new file mode 100644 index 00000000000..6142df26052 --- /dev/null +++ b/manager/src/main/java/org/apache/hertzbeat/manager/service/impl/BulletinServiceImpl.java @@ -0,0 +1,258 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.hertzbeat.manager.service.impl; + +import com.fasterxml.jackson.core.type.TypeReference; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.TreeMap; +import java.util.stream.Collectors; +import lombok.extern.slf4j.Slf4j; +import org.apache.hertzbeat.common.entity.manager.Monitor; +import org.apache.hertzbeat.common.entity.manager.bulletin.Bulletin; +import org.apache.hertzbeat.common.entity.manager.bulletin.BulletinDto; +import org.apache.hertzbeat.common.entity.manager.bulletin.BulletinMetricsData; +import org.apache.hertzbeat.common.entity.manager.bulletin.BulletinVo; +import org.apache.hertzbeat.common.entity.message.CollectRep; +import org.apache.hertzbeat.common.util.JsonUtil; +import org.apache.hertzbeat.common.util.SnowFlakeIdGenerator; +import org.apache.hertzbeat.manager.dao.BulletinDao; +import org.apache.hertzbeat.manager.service.BulletinService; +import org.apache.hertzbeat.manager.service.MonitorService; +import org.apache.hertzbeat.warehouse.store.realtime.RealTimeDataReader; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +/** + * Bulletin Service Implementation + */ + +@Service +@Slf4j +public class BulletinServiceImpl implements BulletinService { + + private static final String NO_DATA = "No Data"; + + private static final String EMPTY_STRING = ""; + + @Autowired + private BulletinDao bulletinDao; + + @Autowired + private MonitorService monitorService; + + @Autowired + private RealTimeDataReader realTimeDataReader; + + + /** + * validate Bulletin + */ + @Override + public void validate(BulletinDto bulletinDto) throws IllegalArgumentException { + if (bulletinDto == null) { + throw new IllegalArgumentException("Bulletin cannot be null"); + } + if (bulletinDto.getApp() == null || bulletinDto.getApp().isEmpty()) { + throw new IllegalArgumentException("Bulletin app cannot be null or empty"); + } + if (bulletinDto.getFields() == null || bulletinDto.getFields().isEmpty()) { + throw new IllegalArgumentException("Bulletin fields cannot be null or empty"); + } + if (bulletinDto.getMonitorIds() == null || bulletinDto.getMonitorIds().isEmpty()) { + throw new IllegalArgumentException("Bulletin monitorIds cannot be null or empty"); + } + } + + + /** + * Pageable query Bulletin + */ + @Override + public Bulletin getBulletinByName(String name) { + return bulletinDao.findByName(name); + } + + /** + * Get all names + */ + @Override + public List getAllNames() { + return bulletinDao.findAll().stream().map(Bulletin::getName).distinct().toList(); + } + + + /** + * Save Bulletin + */ + @Override + @Transactional(rollbackFor = Exception.class) + public void editBulletin(BulletinDto bulletinDto) { + try { + //TODO: update bulletin + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * Add Bulletin + */ + @Override + @Transactional(rollbackFor = Exception.class) + public void addBulletin(BulletinDto bulletinDto) { + try { + Bulletin bulletin = new Bulletin(); + bulletin.setName(bulletinDto.getName()); + bulletin.setId(SnowFlakeIdGenerator.generateId()); + Map> map = bulletinDto.getFields(); + Map> sortedMap = new TreeMap<>(map); + String fields = JsonUtil.toJson(sortedMap); + bulletin.setFields(fields); + bulletin.setMonitorIds(bulletinDto.getMonitorIds()); + bulletin.setApp(bulletinDto.getApp()); + bulletinDao.save(bulletin); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * Dynamic conditional query + * + * @param specification Query conditions + * @param pageRequest Paging parameters + * @return The query results + */ + @Override + public Page getBulletins(Specification specification, PageRequest pageRequest) { + List voList = new ArrayList<>(); + Page bulletinPage = Page.empty(pageRequest); + try { + bulletinPage = bulletinDao.findAll(specification, pageRequest); + voList = bulletinPage.stream().map(bulletin -> { + BulletinVo vo = new BulletinVo(); + vo.setId(bulletin.getId()); + vo.setName(bulletin.getName()); + vo.setTags(bulletin.getTags()); + vo.setMonitorId(bulletin.getMonitorIds()); + vo.setApp(bulletin.getApp()); + return vo; + }).collect(Collectors.toList()); + } catch (Exception e) { + log.error("Failed to query bulletin: {}", e.getLocalizedMessage(), e); + } + long total = bulletinPage.getTotalElements(); + return new PageImpl<>(voList, pageRequest, total); + } + + /** + * deal with the bulletin + * + */ + @Override + public BulletinMetricsData buildBulletinMetricsData(BulletinMetricsData.BulletinMetricsDataBuilder contentBuilder, Bulletin bulletin) { + List dataList = new ArrayList<>(); + for (Long monitorId : bulletin.getMonitorIds()) { + Monitor monitor = monitorService.getMonitor(monitorId); + BulletinMetricsData.Data.DataBuilder dataBuilder = BulletinMetricsData.Data.builder() + .monitorId(monitorId) + .monitorName(monitor.getName()) + .host(monitor.getHost()); + + List metrics = new ArrayList<>(); + Map> fieldMap = JsonUtil.fromJson(bulletin.getFields(), new TypeReference<>() {}); + + if (fieldMap != null) { + for (Map.Entry> entry : fieldMap.entrySet()) { + String metric = entry.getKey(); + List fields = entry.getValue(); + BulletinMetricsData.Metric.MetricBuilder metricBuilder = BulletinMetricsData.Metric.builder() + .name(metric); + CollectRep.MetricsData currentMetricsData = realTimeDataReader.getCurrentMetricsData(monitorId, metric); + + List> fieldsList; + if (currentMetricsData != null) { + fieldsList = currentMetricsData.getValuesList().stream() + .map(valueRow -> { + List fieldList = currentMetricsData.getFieldsList().stream() + .map(field -> BulletinMetricsData.Field.builder() + .key(field.getName()) + .unit(field.getUnit()) + .build()) + .toList(); + + for (int i = 0; i < fieldList.size(); i++) { + fieldList.get(i).setValue(valueRow.getColumns(i)); + } + return fieldList; + }) + .toList(); + } else { + fieldsList = Collections.singletonList(fields.stream() + .map(field -> BulletinMetricsData.Field.builder() + .key(field) + .unit("") + .value("NO_DATA") + .build()) + .toList()); + } + + metricBuilder.fields(fieldsList); + metrics.add(metricBuilder.build()); + } + } + dataBuilder.metrics(metrics); + dataList.add(dataBuilder.build()); + } + contentBuilder.content(dataList); + return contentBuilder.build(); + } + + + /** + * Get Bulletin by id + * + */ + @Override + public Optional getBulletinById(Long id) { + return bulletinDao.findById(id); + } + + /** + * delete Bulletin by names + * + */ + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteBulletinByName(List names) { + try { + bulletinDao.deleteByNameIn(names); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/manager/src/main/resources/sureness.yml b/manager/src/main/resources/sureness.yml index c7253ff88c8..d96f2a331d8 100644 --- a/manager/src/main/resources/sureness.yml +++ b/manager/src/main/resources/sureness.yml @@ -58,6 +58,10 @@ resourceRole: - /api/status/page/**===post===[admin,user] - /api/status/page/**===put===[admin,user] - /api/status/page/**===delete===[admin] + - /api/bulletin/**===get===[admin,user,guest] + - /api/bulletin/**===post===[admin,user] + - /api/bulletin/**===put===[admin,user] + - /api/bulletin/**===delete===[admin] # config the resource restful api that need bypass auth protection # rule: api===method diff --git a/script/sureness.yml b/script/sureness.yml index c7253ff88c8..cb4024c5a9c 100644 --- a/script/sureness.yml +++ b/script/sureness.yml @@ -58,7 +58,11 @@ resourceRole: - /api/status/page/**===post===[admin,user] - /api/status/page/**===put===[admin,user] - /api/status/page/**===delete===[admin] - + - /api/bulletin/**===get===[admin,user,guest] + - /api/bulletin/**===post===[admin,user] + - /api/bulletin/**===put===[admin,user] + - /api/bulletin/**===delete===[admin] + # config the resource restful api that need bypass auth protection # rule: api===method # eg: /api/v1/source3===get means /api/v1/source3===get can be access by anyone, no need auth. diff --git a/web-app/src/app/pojo/BulletinDefine.ts b/web-app/src/app/pojo/BulletinDefine.ts new file mode 100644 index 00000000000..0c414957775 --- /dev/null +++ b/web-app/src/app/pojo/BulletinDefine.ts @@ -0,0 +1,28 @@ +/* + * 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. + */ + +import { Fields } from './Fields'; + +export class BulletinDefine { + id!: number; + name!: string; + monitorIds!: number[]; + app!: string; + fields: Fields = {}; +} diff --git a/web-app/src/app/pojo/Fields.ts b/web-app/src/app/pojo/Fields.ts new file mode 100644 index 00000000000..19c5160c324 --- /dev/null +++ b/web-app/src/app/pojo/Fields.ts @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export interface Fields { + [key: string]: string[]; +} diff --git a/web-app/src/app/routes/bulletin/bulletin.component.html b/web-app/src/app/routes/bulletin/bulletin.component.html new file mode 100644 index 00000000000..f4db668331f --- /dev/null +++ b/web-app/src/app/routes/bulletin/bulletin.component.html @@ -0,0 +1,237 @@ + + + + + + + + + + + + + + + + + + + + + + + + App + Host + + + {{ metric }} + + + + + + + + {{ field }} + + + + + + + + + {{ content.monitorName }} + {{ content.host }} + + + + + + No Data Available + + + {{ item.value }} + {{ item.unit }} + + + + + + + + + + + + + + + +
+
+ + {{ 'bulletin.name' | i18n }} + + + + + + + {{ 'bulletin.monitor.type' | i18n }} + + + + + + + + + + + {{ 'bulletin.monitor.name' | i18n }} + + + + + + + + + + + {{ 'bulletin.monitor.metrics' | i18n }} + + + + + + + {{ node.title }} + + + + + + + +
+
+
+ + + +
+
+ + {{ 'bulletin.name' | i18n }} + + + + + + +
+
+
diff --git a/web-app/src/app/routes/bulletin/bulletin.component.less b/web-app/src/app/routes/bulletin/bulletin.component.less new file mode 100644 index 00000000000..0b13dcbfc01 --- /dev/null +++ b/web-app/src/app/routes/bulletin/bulletin.component.less @@ -0,0 +1,17 @@ +@table-padding: 8px; +.table { + width: 100%; + table-layout: auto; + + th { + white-space: nowrap; + padding: @table-padding; + text-align: center; + } + + td { + white-space: nowrap; + padding: @table-padding; + text-align: center; + } +} diff --git a/web-app/src/app/routes/bulletin/bulletin.component.spec.ts b/web-app/src/app/routes/bulletin/bulletin.component.spec.ts new file mode 100644 index 00000000000..5e1fc104026 --- /dev/null +++ b/web-app/src/app/routes/bulletin/bulletin.component.spec.ts @@ -0,0 +1,43 @@ +/* + * 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. + */ + +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { BulletinComponent } from './bulletin.component'; + +describe('BulletinComponent', () => { + let component: BulletinComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [BulletinComponent] + }).compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(BulletinComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/web-app/src/app/routes/bulletin/bulletin.component.ts b/web-app/src/app/routes/bulletin/bulletin.component.ts new file mode 100644 index 00000000000..f48f6ec9e35 --- /dev/null +++ b/web-app/src/app/routes/bulletin/bulletin.component.ts @@ -0,0 +1,509 @@ +/* + * 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. + */ + +import { Component, Inject, OnInit } from '@angular/core'; +import { I18NService } from '@core'; +import { ALAIN_I18N_TOKEN } from '@delon/theme'; +import { NzModalService } from 'ng-zorro-antd/modal'; +import { NzNotificationService } from 'ng-zorro-antd/notification'; +import { NzTableQueryParams } from 'ng-zorro-antd/table'; +import { TransferChange } from 'ng-zorro-antd/transfer'; +import { NzFormatEmitEvent, NzTreeNode, NzTreeNodeOptions } from 'ng-zorro-antd/tree'; +import { finalize } from 'rxjs/operators'; + +import { BulletinDefine } from '../../pojo/BulletinDefine'; +import { Fields } from '../../pojo/Fields'; +import { Monitor } from '../../pojo/Monitor'; +import { AppDefineService } from '../../service/app-define.service'; +import { BulletinDefineService } from '../../service/bulletin-define.service'; +import { MonitorService } from '../../service/monitor.service'; + +@Component({ + selector: 'app-bulletin', + templateUrl: './bulletin.component.html', + styleUrls: ['./bulletin.component.less'] +}) +export class BulletinComponent implements OnInit { + constructor( + private modal: NzModalService, + private notifySvc: NzNotificationService, + private appDefineSvc: AppDefineService, + private monitorSvc: MonitorService, + private bulletinDefineSvc: BulletinDefineService, + @Inject(ALAIN_I18N_TOKEN) private i18nSvc: I18NService + ) {} + search!: string; + tabs!: string[]; + metricsData!: any; + tableLoading: boolean = true; + bulletinName!: string; + deleteBulletinNames: string[] = []; + isAppListLoading = false; + isMonitorListLoading = false; + treeNodes!: NzTreeNodeOptions[]; + hierarchies: NzTreeNodeOptions[] = []; + appMap = new Map(); + appEntries: Array<{ value: any; key: string }> = []; + checkedNodeList: NzTreeNode[] = []; + monitors: Monitor[] = []; + metrics = new Set(); + tempMetrics = new Set(); + fields: Fields = {}; + pageIndex: number = 1; + pageSize: number = 8; + total: number = 0; + + ngOnInit() { + this.loadTabs(); + } + + sync() { + this.loadData(this.pageIndex - 1, this.pageSize); + } + + onNewBulletinDefine() { + this.resetManageModalData(); + this.isManageModalAdd = true; + this.isManageModalVisible = true; + this.isManageModalOkLoading = false; + } + + onEditBulletinDefine() { + if (this.currentDefine) { + this.define = this.currentDefine; + this.onAppChange(this.define.app); + // this.tempMetrics.add(...this.define.fields.keys()); + this.isManageModalAdd = false; + this.isManageModalVisible = true; + this.isManageModalOkLoading = false; + } + } + + deleteBulletinDefines(defineNames: string[]) { + if (defineNames == null || defineNames.length == 0) { + this.notifySvc.warning(this.i18nSvc.fanyi('common.notify.no-select-delete'), ''); + return; + } + const deleteDefines$ = this.bulletinDefineSvc.deleteBulletinDefines(defineNames).subscribe( + message => { + deleteDefines$.unsubscribe(); + if (message.code === 0) { + this.notifySvc.success(this.i18nSvc.fanyi('common.notify.delete-success'), ''); + this.loadTabs(); + } else { + this.notifySvc.error(this.i18nSvc.fanyi('common.notify.delete-fail'), message.msg); + } + }, + error => { + deleteDefines$.unsubscribe(); + this.notifySvc.error(this.i18nSvc.fanyi('common.notify.delete-fail'), error.msg); + } + ); + } + + isManageModalVisible = false; + isManageModalOkLoading = false; + isManageModalAdd = true; + define: BulletinDefine = new BulletinDefine(); + currentDefine!: BulletinDefine | null; + + onManageModalCancel() { + this.isManageModalVisible = false; + } + + resetManageModalData() { + this.define = new BulletinDefine(); + this.define.monitorIds = []; + this.hierarchies = []; + this.treeNodes = []; + } + + onManageModalOk() { + this.isManageModalOkLoading = true; + this.define.fields = this.fields; + if (this.isManageModalAdd) { + const modalOk$ = this.bulletinDefineSvc + .newBulletinDefine(this.define) + .pipe( + finalize(() => { + modalOk$.unsubscribe(); + this.isManageModalOkLoading = false; + }) + ) + .subscribe( + message => { + if (message.code === 0) { + this.notifySvc.success(this.i18nSvc.fanyi('common.notify.new-success'), ''); + this.isManageModalVisible = false; + this.resetManageModalData(); + this.loadTabs(); + } else { + this.notifySvc.error(this.i18nSvc.fanyi('common.notify.new-fail'), message.msg); + } + }, + error => { + this.notifySvc.error(this.i18nSvc.fanyi('common.notify.new-fail'), error.msg); + } + ); + } else { + const modalOk$ = this.bulletinDefineSvc + .editBulletinDefine(this.define) + .pipe( + finalize(() => { + modalOk$.unsubscribe(); + this.isManageModalOkLoading = false; + }) + ) + .subscribe( + message => { + if (message.code === 0) { + this.isManageModalVisible = false; + this.notifySvc.success(this.i18nSvc.fanyi('common.notify.edit-success'), ''); + this.loadData(this.pageIndex - 1, this.pageSize); + } else { + this.notifySvc.error(this.i18nSvc.fanyi('common.notify.edit-fail'), message.msg); + } + }, + error => { + this.notifySvc.error(this.i18nSvc.fanyi('common.notify.edit-fail'), error.msg); + } + ); + } + } + + onSearchAppDefines(): void { + this.appDefineSvc + .getAppDefines(this.i18nSvc.defaultLang) + .pipe() + .subscribe( + message => { + if (message.code === 0) { + this.appMap = message.data; + this.appEntries = Object.entries(this.appMap).map(([key, value]) => ({ key, value })); + if (this.appEntries != null) { + this.isAppListLoading = true; + } + } else { + console.warn(message.msg); + } + }, + error => { + console.warn(error.msg); + } + ); + } + + onSearchMonitorsByApp(app: string): void { + this.monitorSvc + .getMonitorsByApp(app) + .pipe() + .subscribe( + message => { + if (message.code === 0) { + this.monitors = message.data; + if (this.monitors != null) { + this.isMonitorListLoading = true; + } + } else { + console.warn(message.msg); + } + }, + error => { + console.warn(error.msg); + } + ); + } + + onAppChange(app: string): void { + if (app) { + this.onSearchMonitorsByApp(app); + this.onSearchTreeNodes(app); + } else { + this.hierarchies = []; + this.treeNodes = []; + } + } + + onSearchTreeNodes(app: string): void { + this.appDefineSvc + .getAppHierarchyByName(this.i18nSvc.defaultLang, app) + .pipe() + .subscribe( + message => { + if (message.code === 0) { + this.hierarchies = this.transformToTransferItems(message.data); + this.treeNodes = this.generateTree(this.hierarchies); + } else { + console.warn(message.msg); + } + }, + error => { + console.warn(error.msg); + } + ); + } + + transformToTransferItems(data: any[]): NzTreeNodeOptions[] { + const result: NzTreeNodeOptions[] = []; + let currentId = 1; + + const traverse = (nodes: any[], parentKey: string | null = null, parentId: number | null = null) => { + nodes.forEach(node => { + const key = parentKey ? `${parentKey}` : node.value; + const isRootNode = parentId === null; + const item: NzTreeNodeOptions = { + id: currentId++, + key, + value: node.value, + title: node.label, + isLeaf: node.isLeaf, + parentId, + disabled: isRootNode + }; + result.push(item); + + if (node.children) { + traverse(node.children, key, item.id); + } + }); + }; + + if (data[0] && data[0].children) { + data = data[0].children; + traverse(data); + } + + return result; + } + + private generateTree(arr: NzTreeNodeOptions[]): NzTreeNodeOptions[] { + const tree: NzTreeNodeOptions[] = []; + const treeNodes: any = {}; + let leftElem: NzTreeNodeOptions; + let rightElem: NzTreeNodeOptions; + + for (let i = 0, len = arr.length; i < len; i++) { + leftElem = arr[i]; + treeNodes[leftElem.id] = { ...leftElem }; + treeNodes[leftElem.id].children = []; + } + + for (const id in treeNodes) { + if (treeNodes.hasOwnProperty(id)) { + rightElem = treeNodes[id]; + if (rightElem.parentId) { + treeNodes[rightElem.parentId].children.push(rightElem); + } else { + tree.push(rightElem); + } + } + } + return tree; + } + + treeCheckBoxChange(event: NzFormatEmitEvent, onItemSelect: (item: NzTreeNodeOptions) => void): void { + this.checkBoxChange(event.node!, onItemSelect); + } + + checkBoxChange(node: NzTreeNode, onItemSelect: (item: NzTreeNodeOptions) => void): void { + if (node.isDisabled) { + return; + } + + if (node.isChecked) { + this.checkedNodeList.push(node); + } else { + const idx = this.checkedNodeList.indexOf(node); + if (idx !== -1) { + this.checkedNodeList.splice(idx, 1); + } + } + const item = this.hierarchies.find(w => w.id === node.origin.id); + onItemSelect(item!); + } + + transferChange(ret: TransferChange): void { + // add + if (ret.to === 'right') { + this.checkedNodeList.forEach(node => { + node.isDisabled = true; + node.isChecked = true; + this.tempMetrics.add(node.key); + + if (!this.fields[node.key]) { + this.fields[node.key] = []; + } + if (!this.fields[node.key].includes(node.origin.value)) { + this.fields[node.key].push(node.origin.value); + } + }); + } + // delete + else if (ret.to === 'left') { + this.checkedNodeList.forEach(node => { + node.isDisabled = false; + node.isChecked = false; + this.tempMetrics.delete(node.key); + + if (this.fields[node.key]) { + const index = this.fields[node.key].indexOf(node.origin.value); + if (index > -1) { + this.fields[node.key].splice(index, 1); + } + // 如果该 key 下的数组为空,则删除该 key + if (this.fields[node.key].length === 0) { + delete this.fields[node.key]; + } + } + }); + } + } + + loadTabs() { + const allNames$ = this.bulletinDefineSvc.getAllNames().subscribe( + message => { + allNames$.unsubscribe(); + if (message.code === 0) { + this.tabs = message.data; + if (this.tabs != null) { + this.bulletinName = this.tabs[0]; + } + this.loadData(this.pageIndex - 1, this.pageSize); + } else { + this.notifySvc.error(this.i18nSvc.fanyi('common.notify.get-fail'), message.msg); + } + }, + error => { + allNames$.unsubscribe(); + this.notifySvc.error(this.i18nSvc.fanyi('common.notify.get-fail'), error.msg); + } + ); + } + + loadData(page: number, size: number) { + this.tableLoading = true; + this.metricsData = []; + this.currentDefine = null; + this.metrics = new Set(); + if (this.bulletinName != null) { + const defineData$ = this.bulletinDefineSvc.getBulletinDefine(this.bulletinName).subscribe( + message => { + if (message.code === 0) { + this.currentDefine = message.data; + + const metricData$ = this.bulletinDefineSvc.getMonitorMetricsData(this.bulletinName, page, size).subscribe( + message => { + metricData$.unsubscribe(); + if (message.code === 0 && message.data) { + (this.metricsData = message.data.content).forEach((item: any) => { + item.metrics.forEach((metric: any) => { + this.metrics.add(metric.name); + }); + }); + } else if (message.code !== 0) { + this.notifySvc.warning(`${message.msg}`, ''); + console.info(`${message.msg}`); + } + this.tableLoading = false; + }, + error => { + console.error(error.msg); + metricData$.unsubscribe(); + this.tableLoading = false; + } + ); + } else { + this.notifySvc.warning(`${message.msg}`, ''); + console.info(`${message.msg}`); + } + }, + error => { + console.error(error.msg); + defineData$.unsubscribe(); + this.tableLoading = false; + } + ); + } + this.tableLoading = false; + } + + getKeys(metricName: string): string[] { + const result = new Set(); + this.metricsData.forEach((item: any) => { + item.metrics.forEach((metric: any) => { + if (metric.name === metricName) { + metric.fields.forEach((fieldGroup: any) => { + fieldGroup.forEach((field: any) => { + result.add(field.key); + }); + }); + } + }); + }); + return Array.from(result); + } + + onTablePageChange(params: NzTableQueryParams): void { + const { pageSize, pageIndex } = params; + + if (pageIndex !== this.pageIndex || pageSize !== this.pageSize) { + this.pageIndex = pageIndex; + this.pageSize = pageSize; + this.loadData(pageIndex - 1, pageSize); + } + } + + isBatchDeleteModalVisible: boolean = false; + isBatchDeleteModalOkLoading: boolean = false; + + onDeleteBulletinDefines() { + this.modal.confirm({ + nzTitle: this.i18nSvc.fanyi('common.confirm.delete'), + nzOkText: this.i18nSvc.fanyi('common.button.ok'), + nzCancelText: this.i18nSvc.fanyi('common.button.cancel'), + nzOkDanger: true, + nzOkType: 'primary', + nzClosable: false, + nzOnOk: () => this.deleteBulletinDefines([this.bulletinName]) + }); + } + + onBatchDeleteBulletinDefines() { + this.isBatchDeleteModalVisible = true; + } + + onBatchDeleteModalCancel() { + this.isBatchDeleteModalVisible = false; + } + + onBatchDeleteModalOk() { + this.deleteBulletinDefines(this.deleteBulletinNames); + this.isBatchDeleteModalOkLoading = false; + this.isBatchDeleteModalVisible = false; + } + + protected readonly Array = Array; + + onTabChange($event: number) { + this.bulletinName = this.tabs[$event]; + this.metricsData = []; + this.loadData(this.pageIndex - 1, this.pageSize); + console.log(this.metricsData); + } +} diff --git a/web-app/src/app/routes/routes-routing.module.ts b/web-app/src/app/routes/routes-routing.module.ts index b8a87eb0f3d..f4f86a0b956 100644 --- a/web-app/src/app/routes/routes-routing.module.ts +++ b/web-app/src/app/routes/routes-routing.module.ts @@ -6,6 +6,7 @@ import { DetectAuthGuard } from '../core/guard/detect-auth-guard'; import { LayoutBasicComponent } from '../layout/basic/basic.component'; import { LayoutBlankComponent } from '../layout/blank/blank.component'; import { LayoutPassportComponent } from '../layout/passport/passport.component'; +import { BulletinComponent } from './bulletin/bulletin.component'; import { DashboardComponent } from './dashboard/dashboard.component'; import { UserLockComponent } from './passport/lock/lock.component'; import { UserLoginComponent } from './passport/login/login.component'; @@ -19,6 +20,7 @@ const routes: Routes = [ children: [ { path: '', redirectTo: 'dashboard', pathMatch: 'full' }, { path: 'dashboard', component: DashboardComponent, data: { titleI18n: 'menu.dashboard' } }, + { path: 'bulletin', component: BulletinComponent, data: { titleI18n: 'menu.dashboard' } }, { path: 'exception', loadChildren: () => import('./exception/exception.module').then(m => m.ExceptionModule) }, { path: 'monitors', loadChildren: () => import('./monitor/monitor.module').then(m => m.MonitorModule) }, { path: 'alert', loadChildren: () => import('./alert/alert.module').then(m => m.AlertModule) }, diff --git a/web-app/src/app/routes/routes.module.ts b/web-app/src/app/routes/routes.module.ts index 0d45cb6401e..37999b486b5 100644 --- a/web-app/src/app/routes/routes.module.ts +++ b/web-app/src/app/routes/routes.module.ts @@ -1,24 +1,32 @@ +import { CommonModule } from '@angular/common'; import { NgModule, Type } from '@angular/core'; // eslint-disable-next-line import/order import { SharedModule } from '@shared'; import { TagCloudComponent } from 'angular-tag-cloud-module'; +import { NzCascaderModule } from 'ng-zorro-antd/cascader'; import { NzCollapseModule } from 'ng-zorro-antd/collapse'; import { NzDividerModule } from 'ng-zorro-antd/divider'; import { NzListModule } from 'ng-zorro-antd/list'; +import { NzRadioModule } from 'ng-zorro-antd/radio'; +import { NzSwitchComponent } from 'ng-zorro-antd/switch'; import { NzTagModule } from 'ng-zorro-antd/tag'; import { NzTimelineModule } from 'ng-zorro-antd/timeline'; +import { NzTransferModule } from 'ng-zorro-antd/transfer'; +import { NzTreeComponent } from 'ng-zorro-antd/tree'; +import { NzUploadModule } from 'ng-zorro-antd/upload'; import { NgxEchartsModule } from 'ngx-echarts'; import { SlickCarouselModule } from 'ngx-slick-carousel'; import { LayoutModule } from '../layout/layout.module'; +import { BulletinComponent } from './bulletin/bulletin.component'; import { DashboardComponent } from './dashboard/dashboard.component'; import { UserLockComponent } from './passport/lock/lock.component'; import { UserLoginComponent } from './passport/login/login.component'; import { RouteRoutingModule } from './routes-routing.module'; import { StatusPublicComponent } from './status-public/status-public.component'; -const COMPONENTS: Array> = [DashboardComponent, UserLoginComponent, UserLockComponent, StatusPublicComponent]; +const COMPONENTS: Array> = [DashboardComponent, UserLoginComponent, UserLockComponent, StatusPublicComponent, BulletinComponent]; @NgModule({ imports: [ @@ -32,7 +40,14 @@ const COMPONENTS: Array> = [DashboardComponent, UserLoginComponent, U NzDividerModule, LayoutModule, NzCollapseModule, - NzListModule + NzListModule, + CommonModule, + NzRadioModule, + NzUploadModule, + NzCascaderModule, + NzTransferModule, + NzSwitchComponent, + NzTreeComponent ], declarations: COMPONENTS }) diff --git a/web-app/src/app/service/app-define.service.ts b/web-app/src/app/service/app-define.service.ts index a68a1998a38..a832fd40d2e 100644 --- a/web-app/src/app/service/app-define.service.ts +++ b/web-app/src/app/service/app-define.service.ts @@ -97,4 +97,22 @@ export class AppDefineService { const options = { params: httpParams }; return this.http.get>(app_hierarchy, options); } + + public getAppHierarchyByName(lang: string | undefined, app: string): Observable> { + if (lang == undefined) { + lang = 'en_US'; + } + let httpParams = new HttpParams().append('lang', lang); + const options = { params: httpParams }; + return this.http.get>(`${app_hierarchy}/${app}`, options); + } + + public getAppDefines(lang: string | undefined): Observable> { + if (lang == undefined) { + lang = 'en_US'; + } + let httpParams = new HttpParams().append('lang', lang); + const options = { params: httpParams }; + return this.http.get>(`/apps/defines`, options); + } } diff --git a/web-app/src/app/service/bulletin-define.service.ts b/web-app/src/app/service/bulletin-define.service.ts new file mode 100644 index 00000000000..cd92c4c53eb --- /dev/null +++ b/web-app/src/app/service/bulletin-define.service.ts @@ -0,0 +1,87 @@ +/* + * 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. + */ + +import { HttpClient, HttpParams } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; + +import { BulletinDefine } from '../pojo/BulletinDefine'; +import { Message } from '../pojo/Message'; +import { Monitor } from '../pojo/Monitor'; +import { Page } from '../pojo/Page'; + +const bulletin_define_uri = '/bulletin'; + +@Injectable({ + providedIn: 'root' +}) +export class BulletinDefineService { + constructor(private http: HttpClient) {} + + public newBulletinDefine(body: BulletinDefine) { + return this.http.post>(bulletin_define_uri, body); + } + + public editBulletinDefine(body: BulletinDefine) { + return this.http.put>(bulletin_define_uri, body); + } + + public getBulletinDefine(name: string) { + return this.http.get>(`${bulletin_define_uri}/${name}`); + } + + public deleteBulletinDefines(names: string[]): Observable> { + let params = new HttpParams(); + names.forEach(name => { + params = params.append('names', name); + }); + + return this.http.delete>(bulletin_define_uri, { params }); + } + + public getMonitorMetricsData( + name: string, + pageIndex: number, + pageSize: number, + sortField?: string | null, + sortOrder?: string | null + ): Observable>> { + pageIndex = pageIndex ? pageIndex : 0; + pageSize = pageSize ? pageSize : 8; + // 注意HttpParams是不可变对象 需要保存set后返回的对象为最新对象 + let httpParams = new HttpParams(); + httpParams = httpParams.appendAll({ + name: name, + pageIndex: pageIndex, + pageSize: pageSize + }); + if (sortField != null && sortOrder != null) { + httpParams = httpParams.appendAll({ + sort: sortField, + order: sortOrder == 'ascend' ? 'asc' : 'desc' + }); + } + const options = { params: httpParams }; + return this.http.get>(`${bulletin_define_uri}/metrics`, options); + } + + public getAllNames(): Observable> { + return this.http.get>(`${bulletin_define_uri}/names`); + } +} diff --git a/web-app/src/app/service/monitor.service.ts b/web-app/src/app/service/monitor.service.ts index 6b698be61fc..0849145cd79 100644 --- a/web-app/src/app/service/monitor.service.ts +++ b/web-app/src/app/service/monitor.service.ts @@ -47,6 +47,10 @@ export class MonitorService { return this.http.put>(monitor_uri, body); } + public getMonitorByApp(app: string): Observable> { + return this.http.get>(`${monitor_uri}/metric/${app}`); + } + public deleteMonitor(monitorId: number): Observable> { return this.http.delete>(`${monitor_uri}/${monitorId}`); } diff --git a/web-app/src/assets/app-data.json b/web-app/src/assets/app-data.json index 294d9922ff6..200d52c126f 100644 --- a/web-app/src/assets/app-data.json +++ b/web-app/src/assets/app-data.json @@ -36,6 +36,12 @@ "icon": "anticon-laptop", "link": "/monitors" }, + { + "text": "Bulletin", + "i18n": "menu.monitor.bulletin", + "icon": "anticon-book", + "link": "/bulletin" + }, { "text": "Define", "i18n": "menu.advanced.define", diff --git a/web-app/src/assets/i18n/en-US.json b/web-app/src/assets/i18n/en-US.json index 4a9cf506e35..d73581fa645 100644 --- a/web-app/src/assets/i18n/en-US.json +++ b/web-app/src/assets/i18n/en-US.json @@ -10,6 +10,7 @@ "monitor": { "": "Monitoring", "center": "Monitor Center", + "bulletin": "Bulletin", "service": "Service Monitor", "db": "DB Monitor", "os": "OS Monitor", @@ -43,6 +44,20 @@ "silence": "Alarm Silence", "dispatch": "Notification" }, + "bulletin": { + "new": "Add New Bulletin Item", + "edit": "Edit Bulletin Item", + "delete": "Delete Bulletin Item", + "batch.delete": "Batch delete Bulletin Item", + "name": "Bulletin Name", + "name.placeholder": "Please enter a custom bulletin name", + "monitor.type": "Monitor Type", + "monitor.name": "Monitor Task Name", + "monitor.metrics": "Monitor Metrics", + "help.message.content": "Custom Monitoring Bulletin", + "help.content": "Custom monitoring bulletin, displaying selected metrics of a specific monitor in table form", + "help.link": "" + }, "advanced": { "": "Advanced", "collector": "Collector Cluster", diff --git a/web-app/src/assets/i18n/zh-CN.json b/web-app/src/assets/i18n/zh-CN.json index 8218fe13370..7271cc1ee48 100644 --- a/web-app/src/assets/i18n/zh-CN.json +++ b/web-app/src/assets/i18n/zh-CN.json @@ -10,6 +10,7 @@ "monitor": { "": "监控", "center": "监控中心", + "bulletin": "自定义看板", "service": "应用服务监控", "db": "数据库监控", "os": "操作系统监控", @@ -130,6 +131,20 @@ "2": "警告告警" } }, + "bulletin": { + "new": "新增看板项", + "edit": "编辑看板项", + "delete": "删除看板项", + "batch.delete": "批量删除看板项", + "name": "看板名称", + "name.placeholder": "请输入自定义看板名称", + "monitor.type": "监控类型", + "monitor.name": "监控任务名称", + "monitor.metrics": "监控指标", + "help.message.content": "自定义监控看板", + "help.content": "自定义监控看板,以表格形式展示某种监控的自选指标", + "help.link": "" + }, "question.link": "https://hertzbeat.apache.org/zh-cn/docs/help/issue", "alert.setting.new": "新增阈值规则", "alert.setting.edit": "编辑阈值规则", diff --git a/web-app/src/assets/i18n/zh-TW.json b/web-app/src/assets/i18n/zh-TW.json index 671de56b5fb..60059f4c99d 100644 --- a/web-app/src/assets/i18n/zh-TW.json +++ b/web-app/src/assets/i18n/zh-TW.json @@ -10,6 +10,7 @@ "monitor": { "": "監控", "center": "監控中心", + "bulletin": "自定義看板", "service": "應用服務監控", "db": "數據庫監控", "os": "操作系統監控", @@ -43,6 +44,20 @@ "silence": "告警靜默", "dispatch": "消息通知" }, + "bulletin": { + "new": "新增看板項", + "edit": "編輯看板項", + "delete": "刪除看板項", + "batch.delete": "批量刪除看板項", + "name": "看板名稱", + "name.placeholder": "請輸入自定義看板名稱", + "monitor.type": "監控類型", + "monitor.name": "監控任務名稱", + "monitor.metrics": "監控指標", + "help.message.content": "自定義監控看板", + "help.content": "自定義監控看板,以表格形式展示某種監控的自選指標", + "help.link": "" + }, "advanced": { "": "高级", "collector": "採集集群",