[Feature] Supports Bulletin to view all metrics. (#2584)

Signed-off-by: tomsun28 <tomsun28@outlook.com>
Co-authored-by: YuLuo <yuluo08290126@gmail.com>
Co-authored-by: tomsun28 <tomsun28@outlook.com>
Co-authored-by: kerwin612 <kerwin612@qq.com>
This commit is contained in:
Logic
2024-08-27 06:25:38 +08:00
committed by GitHub
co-authored by YuLuo tomsun28 kerwin612
parent 5eef247a5b
commit 5d416e855b
31 changed files with 2086 additions and 77 deletions
@@ -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;
@@ -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<Long> 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<TagItem> 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;
}
@@ -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<String, List<String>> fields;
/**
* Monitor ids
*/
private List<Long> monitorIds;
}
@@ -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<Data> 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<Metric> 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<List<Field>> 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;
}
}
@@ -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<String> metrics;
/**
* Bulletin tags
*/
private List<TagItem> tags;
/**
* Bulletin monitor ID
*/
private List<Long> monitorId;
/**
* Bulletin monitor name
*/
private String app;
}
@@ -100,11 +100,15 @@ public final class JsonUtil {
* @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 {
@@ -20,6 +20,7 @@ package org.apache.hertzbeat.common.util;
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 @@ class JsonUtilTest {
assertFalse(isJsonStr(jsonString));
String jsonStringArrays = "[{\"name\":\"John\"}, {\"name\":\"Doe\"}]";
assertFalse(isJsonStr(jsonStringArrays));
assertTrue(isJsonStr(jsonStringArrays));
}
}
@@ -25,6 +25,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
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 class AppController {
@Parameter(description = "en: language type",
example = "zh-CN")
@RequestParam(name = "lang", required = false) String lang) {
lang = getLang(lang);
List<Hierarchy> 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<Message<List<Hierarchy>>> 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<Hierarchy> 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<Message<Map<String, String>>> getAllAppDefines(
@Parameter(description = "en: language type",
example = "zh-CN")
@RequestParam(name = "lang", required = false) String lang) {
lang = getLang(lang);
Map<String, String> 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 class AppController {
} else {
lang = "en-US";
}
List<Hierarchy> appHierarchies = appService.getAllAppHierarchy(lang);
return ResponseEntity.ok(Message.success(appHierarchies));
return lang;
}
}
@@ -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<Message<Void>> 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<Message<Void>> 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<Message<Bulletin>> 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<Message<List<String>>> getAllNames() {
List<String> 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<Message<Void>> deleteBulletin(
@Parameter(description = "Bulletin Name", example = "402372614668544")
@RequestParam List<String> 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<Message<?>> 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));
}
}
@@ -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<Bulletin, Long>, JpaSpecificationExecutor<Bulletin> {
/**
* Delete Bulletin by name
*/
void deleteByNameIn(List<String> names);
/**
* Get Bulletin by name
*/
Bulletin findByName(String name);
}
@@ -80,6 +80,14 @@ public interface AppService {
*/
Map<String, String> getI18nResources(String lang);
/**
* Get the I 18 N resources of the monitoring type
*
* @param lang Language type
* @return I18N Resources
*/
Map<String, String> getI18nApps(String lang);
/**
* Query all types of monitoring hierarchy
*
@@ -88,6 +96,15 @@ public interface AppService {
*/
List<Hierarchy> getAllAppHierarchy(String lang);
/**
* Get the monitoring hierarchy based on the monitoring type
*
* @param app monitoring type
* @param lang language
* @return hierarchy information
*/
List<Hierarchy> getAppHierarchy(String app, String lang);
/**
* Get all app define
*
@@ -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<Bulletin> getBulletinById(Long id);
/**
* Get all names
*/
List<String> getAllNames();
/**
* delete Bulletin by id
*/
void deleteBulletinByName(List<String> 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<BulletinVo> getBulletins(Specification<Bulletin> specification, PageRequest pageRequest);
/**
* deal with the bulletin
*/
BulletinMetricsData buildBulletinMetricsData(BulletinMetricsData.BulletinMetricsDataBuilder contentBuilder, Bulletin bulletin);
}
@@ -255,6 +255,20 @@ public class AppServiceImpl implements AppService, CommandLineRunner {
return i18nMap;
}
@Override
public Map<String, String> getI18nApps(String lang) {
Map<String, String> 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<Hierarchy> getAllAppHierarchy(String lang) {
LinkedList<Hierarchy> hierarchies = new LinkedList<>();
@@ -263,82 +277,104 @@ public class AppServiceImpl implements AppService, CommandLineRunner {
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<Hierarchy> hierarchyMetricList = new LinkedList<>();
if (DispatchConstants.PROTOCOL_PROMETHEUS.equalsIgnoreCase(job.getApp())) {
List<Monitor> monitors = monitorDao.findMonitorsByAppEquals(job.getApp());
for (Monitor monitor : monitors) {
List<CollectRep.MetricsData> metricsDataList = warehouseService.queryMonitorMetricsData(monitor.getId());
for (CollectRep.MetricsData metricsData : metricsDataList) {
var hierarchyMetric = new Hierarchy();
hierarchyMetric.setValue(metricsData.getMetrics());
hierarchyMetric.setLabel(metricsData.getMetrics());
List<Hierarchy> 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<Hierarchy> 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);
}
hierarchyMetric.setChildren(hierarchyFieldList);
}
hierarchyMetricList.add(hierarchyMetric);
}
}
hierarchyApp.setChildren(hierarchyMetricList);
hierarchies.add(hierarchyApp);
}
queryAppHierarchy(lang, hierarchies, job);
}
return hierarchies;
}
@Override
public List<Hierarchy> getAppHierarchy(String app, String lang) {
LinkedList<Hierarchy> 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<Hierarchy> 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<Hierarchy> hierarchyMetricList = new LinkedList<>();
if (DispatchConstants.PROTOCOL_PROMETHEUS.equalsIgnoreCase(job.getApp())) {
List<Monitor> monitors = monitorDao.findMonitorsByAppEquals(job.getApp());
for (Monitor monitor : monitors) {
List<CollectRep.MetricsData> metricsDataList = warehouseService.queryMonitorMetricsData(monitor.getId());
for (CollectRep.MetricsData metricsData : metricsDataList) {
var hierarchyMetric = new Hierarchy();
hierarchyMetric.setValue(metricsData.getMetrics());
hierarchyMetric.setLabel(metricsData.getMetrics());
List<Hierarchy> 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<Hierarchy> 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);
}
hierarchyMetric.setChildren(hierarchyFieldList);
}
hierarchyMetricList.add(hierarchyMetric);
}
}
hierarchyApp.setChildren(hierarchyMetricList);
hierarchies.add(hierarchyApp);
}
}
private void combineHierarchyMetrics(List<Hierarchy> hierarchyMetricList, Hierarchy hierarchyMetric) {
Optional<Hierarchy> 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<Hierarchy> children = preHierarchy.getChildren();
Set<String> childrenKey = children.stream().map(Hierarchy::getValue).collect(Collectors.toSet());
Set<String> 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 @@ public class AppServiceImpl implements AppService, CommandLineRunner {
}
}
@Override
public Map<String, Job> getAllAppDefines() {
return appDefines;
@@ -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<String> 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<String, List<String>> map = bulletinDto.getFields();
Map<String, List<String>> 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<BulletinVo> getBulletins(Specification<Bulletin> specification, PageRequest pageRequest) {
List<BulletinVo> voList = new ArrayList<>();
Page<Bulletin> 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<BulletinMetricsData.Data> 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<BulletinMetricsData.Metric> metrics = new ArrayList<>();
Map<String, List<String>> fieldMap = JsonUtil.fromJson(bulletin.getFields(), new TypeReference<>() {});
if (fieldMap != null) {
for (Map.Entry<String, List<String>> entry : fieldMap.entrySet()) {
String metric = entry.getKey();
List<String> fields = entry.getValue();
BulletinMetricsData.Metric.MetricBuilder metricBuilder = BulletinMetricsData.Metric.builder()
.name(metric);
CollectRep.MetricsData currentMetricsData = realTimeDataReader.getCurrentMetricsData(monitorId, metric);
List<List<BulletinMetricsData.Field>> fieldsList;
if (currentMetricsData != null) {
fieldsList = currentMetricsData.getValuesList().stream()
.map(valueRow -> {
List<BulletinMetricsData.Field> 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<Bulletin> getBulletinById(Long id) {
return bulletinDao.findById(id);
}
/**
* delete Bulletin by names
*
*/
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteBulletinByName(List<String> names) {
try {
bulletinDao.deleteByNameIn(names);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
+4
View File
@@ -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
+5 -1
View File
@@ -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.
+28
View File
@@ -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 = {};
}
+22
View File
@@ -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[];
}
@@ -0,0 +1,237 @@
<!--
~ 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.
-->
<app-help-message-show
[help_message_content]="'bulletin.help.message.content' | i18n"
[guild_link]="'bulletin.help.link' | i18n"
[module_name]="'menu.alert.setting'"
[icon_name]="'calculator'"
></app-help-message-show>
<nz-divider></nz-divider>
<app-toolbar>
<ng-template #left>
<button nz-button nzType="primary" (click)="sync()" nz-tooltip [nzTooltipTitle]="'common.refresh' | i18n">
<i nz-icon nzType="sync" nzTheme="outline"></i>
</button>
<button nz-button nzType="primary" (click)="onNewBulletinDefine()">
<i nz-icon nzType="appstore-add" nzTheme="outline"></i>
{{ 'bulletin.new' | i18n }}
</button>
<!--TODO: Wait until the backend interface is improved before releasing it.-->
<!--<button *ngIf="currentDefine" nz-button nzType="primary" (click)="onEditBulletinDefine()">
<i nz-icon nzType="edit" nzTheme="outline"></i>
{{ 'bulletin.edit' | i18n }}
</button>-->
<button *ngIf="currentDefine" nz-button nzType="primary" nzDanger (click)="onDeleteBulletinDefines()">
<i nz-icon nzType="delete" nzTheme="outline"></i>
{{ 'bulletin.delete' | i18n }}
</button>
<button *ngIf="currentDefine" nz-button nzType="primary" nzDanger (click)="onBatchDeleteBulletinDefines()">
<i nz-icon nzType="delete" nzTheme="outline"></i>
{{ 'bulletin.batch.delete' | i18n }}
</button>
</ng-template>
</app-toolbar>
<nz-tabset nzType="card" (nzSelectedIndexChange)="onTabChange($event)">
<nz-tab *ngFor="let tab of tabs" [nzTitle]="tab">
<ng-container *ngIf="metricsData">
<nz-table
#fixedTable
[nzData]="metricsData"
[nzPageIndex]="pageIndex"
[nzPageSize]="pageSize"
[nzTotal]="total"
nzFrontPagination="false"
[nzLoading]="tableLoading"
nzShowSizeChanger
[nzPageSizeOptions]="[10, 20, 50]"
(nzQueryParams)="onTablePageChange($event)"
nzShowPagination="true"
[nzScroll]="{ x: 'auto' }"
nzBordered
class="table"
>
<thead>
<tr>
<th nzAlign="center" nzWidth="7%" [rowSpan]="2">App</th>
<th nzAlign="center" nzWidth="7%" [rowSpan]="2">Host</th>
<ng-container *ngFor="let metric of metrics">
<th nzAlign="center" [colSpan]="getKeys(metric).length">
{{ metric }}
</th>
</ng-container>
</tr>
<tr>
<ng-container *ngFor="let metric of metrics">
<ng-container *ngFor="let field of getKeys(metric)">
<th nzAlign="center" [colSpan]="1">
{{ field }}
</th>
</ng-container>
</ng-container>
</tr>
</thead>
<tbody>
<ng-container *ngFor="let content of fixedTable.data">
<tr>
<td nzAlign="center" [rowSpan]="1">{{ content.monitorName }}</td>
<td nzAlign="center" [rowSpan]="1">{{ content.host }}</td>
<ng-container *ngFor="let metric of content.metrics">
<ng-container *ngFor="let field of metric.fields">
<ng-container *ngFor="let item of field">
<td nzAlign="center">
<ng-container *ngIf="item.value === 'NO_DATA'; else hasData">
<nz-tag nzColor="warning">No Data Available</nz-tag>
</ng-container>
<ng-template #hasData>
{{ item.value }}
<nz-tag *ngIf="item.unit !== ''" nzColor="success">{{ item.unit }}</nz-tag>
</ng-template>
</td>
</ng-container>
</ng-container>
</ng-container>
</tr>
</ng-container>
</tbody>
</nz-table>
</ng-container>
</nz-tab>
</nz-tabset>
<!-- new bulletin modal -->
<nz-modal
[(nzVisible)]="isManageModalVisible"
[nzTitle]="(isManageModalAdd ? 'bulletin.new' : 'bulletin.edit') | i18n"
(nzOnCancel)="onManageModalCancel()"
(nzOnOk)="onManageModalOk()"
nzMaskClosable="false"
nzWidth="70%"
[nzOkLoading]="isManageModalOkLoading"
>
<div *nzModalContent class="-inner-content">
<form nz-form #defineForm="ngForm">
<nz-form-item>
<nz-form-label [nzSpan]="7" nzFor="textInput" nzRequired="true">{{ 'bulletin.name' | i18n }}</nz-form-label>
<nz-form-control [nzSpan]="12" [nzErrorTip]="'validation.required' | i18n">
<input nz-input placeholder="{{ 'bulletin.name.placeholder' | i18n }}" [(ngModel)]="define.name" name="inputText" required />
</nz-form-control>
</nz-form-item>
<nz-form-item>
<nz-form-label [nzSpan]="7" nzFor="dropdown" nzRequired="true">{{ 'bulletin.monitor.type' | i18n }}</nz-form-label>
<nz-form-control [nzSpan]="12" [nzErrorTip]="'validation.required' | i18n">
<nz-select
nzAllowClear
nzShowSearch
name="appDefines"
[(ngModel)]="define.app"
(nzOnSearch)="onSearchAppDefines()"
(ngModelChange)="onAppChange($event)"
required
>
<ng-container *ngFor="let app of appEntries">
<nz-option *ngIf="isAppListLoading" [nzValue]="app.key" [nzLabel]="app.key + '/' + app.value"></nz-option>
</ng-container>
</nz-select>
</nz-form-control>
</nz-form-item>
<nz-form-item>
<nz-form-label [nzSpan]="7" nzFor="dropdown" nzRequired="true">{{ 'bulletin.monitor.name' | i18n }}</nz-form-label>
<nz-form-control [nzSpan]="12" [nzErrorTip]="'validation.required' | i18n">
<nz-select
nzAllowClear
nzShowSearch
name="monitors"
[(ngModel)]="define.monitorIds"
(nzOnSearch)="onSearchMonitorsByApp(define.app)"
nzMode="multiple"
required
>
<ng-container *ngFor="let monitor of monitors">
<nz-option *ngIf="isMonitorListLoading" [nzValue]="monitor.id" [nzLabel]="monitor.name"></nz-option>
</ng-container>
</nz-select>
</nz-form-control>
</nz-form-item>
<nz-form-item>
<nz-form-label [nzSpan]="7" nzFor="dropdown" nzRequired="true">{{ 'bulletin.monitor.metrics' | i18n }}</nz-form-label>
<nz-form-control [nzSpan]="12" [nzErrorTip]="'validation.required' | i18n">
<nz-transfer [nzDataSource]="hierarchies" [nzRenderList]="[leftRenderList, null]" (nzChange)="transferChange($event)">
<ng-template #leftRenderList let-items let-onItemSelect="onItemSelect">
<nz-tree
[nzData]="treeNodes"
nzExpandAll
nzBlockNode
nzCheckable
nzCheckStrictly
[(ngModel)]="tempMetrics"
name="metrics"
(nzCheckBoxChange)="treeCheckBoxChange($event, onItemSelect)"
>
<ng-template let-node>
<span
(click)="checkBoxChange(node, onItemSelect)"
class="ant-tree-node-content-wrapper ant-tree-node-content-wrapper-open"
>
{{ node.title }}
</span>
</ng-template>
</nz-tree>
</ng-template>
</nz-transfer>
</nz-form-control>
</nz-form-item>
</form>
</div>
</nz-modal>
<!-- delete bulletin modal -->
<nz-modal
[(nzVisible)]="isBatchDeleteModalVisible"
[nzTitle]="'bulletin.batch.delete' | i18n"
(nzOnCancel)="onBatchDeleteModalCancel()"
(nzOnOk)="onBatchDeleteModalOk()"
nzMaskClosable="false"
nzWidth="70%"
>
<div *nzModalContent class="-inner-content">
<form nz-form #defineForm="ngForm">
<nz-form-item>
<nz-form-label [nzSpan]="7" nzFor="textInput" nzRequired="true">{{ 'bulletin.name' | i18n }}</nz-form-label>
<nz-form-control [nzSpan]="12" [nzErrorTip]="'validation.required' | i18n">
<nz-select
nzMode="multiple"
nzPlaceHolder="Please select"
[(ngModel)]="this.deleteBulletinNames"
[nzShowSearch]="true"
name="delete"
>
<nz-option *ngFor="let tab of tabs" [nzLabel]="tab" [nzValue]="tab"></nz-option>
</nz-select>
</nz-form-control>
</nz-form-item>
</form>
</div>
</nz-modal>
@@ -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;
}
}
@@ -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<BulletinComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [BulletinComponent]
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(BulletinComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -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<string, string>();
appEntries: Array<{ value: any; key: string }> = [];
checkedNodeList: NzTreeNode[] = [];
monitors: Monitor[] = [];
metrics = new Set<string>();
tempMetrics = new Set<string>();
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<string>();
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<string>();
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);
}
}
@@ -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) },
+17 -2
View File
@@ -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<Type<void>> = [DashboardComponent, UserLoginComponent, UserLockComponent, StatusPublicComponent];
const COMPONENTS: Array<Type<void>> = [DashboardComponent, UserLoginComponent, UserLockComponent, StatusPublicComponent, BulletinComponent];
@NgModule({
imports: [
@@ -32,7 +40,14 @@ const COMPONENTS: Array<Type<void>> = [DashboardComponent, UserLoginComponent, U
NzDividerModule,
LayoutModule,
NzCollapseModule,
NzListModule
NzListModule,
CommonModule,
NzRadioModule,
NzUploadModule,
NzCascaderModule,
NzTransferModule,
NzSwitchComponent,
NzTreeComponent
],
declarations: COMPONENTS
})
@@ -97,4 +97,22 @@ export class AppDefineService {
const options = { params: httpParams };
return this.http.get<Message<any>>(app_hierarchy, options);
}
public getAppHierarchyByName(lang: string | undefined, app: string): Observable<Message<any>> {
if (lang == undefined) {
lang = 'en_US';
}
let httpParams = new HttpParams().append('lang', lang);
const options = { params: httpParams };
return this.http.get<Message<any>>(`${app_hierarchy}/${app}`, options);
}
public getAppDefines(lang: string | undefined): Observable<Message<any>> {
if (lang == undefined) {
lang = 'en_US';
}
let httpParams = new HttpParams().append('lang', lang);
const options = { params: httpParams };
return this.http.get<Message<any>>(`/apps/defines`, options);
}
}
@@ -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<Message<any>>(bulletin_define_uri, body);
}
public editBulletinDefine(body: BulletinDefine) {
return this.http.put<Message<any>>(bulletin_define_uri, body);
}
public getBulletinDefine(name: string) {
return this.http.get<Message<any>>(`${bulletin_define_uri}/${name}`);
}
public deleteBulletinDefines(names: string[]): Observable<Message<any>> {
let params = new HttpParams();
names.forEach(name => {
params = params.append('names', name);
});
return this.http.delete<Message<any>>(bulletin_define_uri, { params });
}
public getMonitorMetricsData(
name: string,
pageIndex: number,
pageSize: number,
sortField?: string | null,
sortOrder?: string | null
): Observable<Message<Page<Monitor>>> {
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<Message<any>>(`${bulletin_define_uri}/metrics`, options);
}
public getAllNames(): Observable<Message<string[]>> {
return this.http.get<Message<string[]>>(`${bulletin_define_uri}/names`);
}
}
@@ -47,6 +47,10 @@ export class MonitorService {
return this.http.put<Message<any>>(monitor_uri, body);
}
public getMonitorByApp(app: string): Observable<Message<any>> {
return this.http.get<Message<any>>(`${monitor_uri}/metric/${app}`);
}
public deleteMonitor(monitorId: number): Observable<Message<any>> {
return this.http.delete<Message<any>>(`${monitor_uri}/${monitorId}`);
}
+6
View File
@@ -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",
+15
View File
@@ -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",
+15
View File
@@ -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": "编辑阈值规则",
+15
View File
@@ -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": "採集集群",