This is an automated email from the ASF dual-hosted git repository.
liutianyou pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/hertzbeat.git
The following commit(s) were added to refs/heads/master by this push:
new 589b0d915 [feature]Support grafana charts for Prometheus (#1658)
589b0d915 is described below
commit 589b0d915d03707ba97882a3f4135cb3aaef1492
Author: Logic <[email protected]>
AuthorDate: Tue Sep 3 23:09:14 2024 +0800
[feature]Support grafana charts for Prometheus (#1658)
Signed-off-by: Logic <[email protected]>
Signed-off-by: tomsun28 <[email protected]>
Co-authored-by: tomsun28 <[email protected]>
Co-authored-by: liutianyou <[email protected]>
Co-authored-by: aias00 <[email protected]>
---
.../common/entity/grafana/GrafanaDashboard.java | 65 +++++
.../common/entity/grafana/ServiceAccount.java | 61 ++++
.../common/entity/grafana/ServiceToken.java | 50 ++++
.../hertzbeat/common/entity/manager/Monitor.java | 9 +
grafana/pom.xml | 56 ++++
.../hertzbeat/grafana/common/CommonConstants.java | 77 +++++
.../grafana/config/GrafanaAutoConfiguration.java | 29 ++
.../hertzbeat/grafana/config/GrafanaInit.java | 65 +++++
.../grafana/config/GrafanaProperties.java | 58 ++++
.../grafana/controller/DashboardController.java | 111 ++++++++
.../controller/ServiceAccountController.java | 119 ++++++++
.../apache/hertzbeat/grafana/dao/DashboardDao.java | 48 ++++
.../hertzbeat/grafana/dao/ServiceAccountDao.java | 38 +++
.../hertzbeat/grafana/dao/ServiceTokenDao.java | 37 +++
.../grafana/service/DashboardService.java | 181 ++++++++++++
.../grafana/service/DatasourceService.java | 134 +++++++++
.../grafana/service/ServiceAccountService.java | 309 +++++++++++++++++++++
.../src/main/resources/META-INF/spring.factories | 17 ++
manager/pom.xml | 5 +
.../manager/service/impl/MonitorServiceImpl.java | 23 +-
manager/src/main/resources/application-test.yml | 6 +
manager/src/main/resources/application.yml | 7 +-
manager/src/main/resources/sureness.yml | 4 +
pom.xml | 7 +
web-app/src/app/pojo/GrafanaDashboard.ts | 26 ++
web-app/src/app/pojo/Monitor.ts | 2 +
web-app/src/app/routes/SafePipe.ts | 31 +++
.../monitor-detail/monitor-detail.component.html | 9 +
.../monitor-detail/monitor-detail.component.ts | 16 ++
.../monitor/monitor-edit/monitor-edit.component.ts | 17 ++
.../monitor-form/monitor-form.component.html | 33 +++
.../monitor/monitor-form/monitor-form.component.ts | 14 +
.../monitor/monitor-list/monitor-list.component.ts | 19 ++
.../monitor/monitor-new/monitor-new.component.ts | 2 +
web-app/src/app/routes/monitor/monitor.module.ts | 2 +
web-app/src/app/service/monitor.service.ts | 9 +
web-app/src/assets/i18n/en-US.json | 8 +-
web-app/src/assets/i18n/zh-CN.json | 6 +
web-app/src/assets/i18n/zh-TW.json | 8 +-
39 files changed, 1714 insertions(+), 4 deletions(-)
diff --git
a/common/src/main/java/org/apache/hertzbeat/common/entity/grafana/GrafanaDashboard.java
b/common/src/main/java/org/apache/hertzbeat/common/entity/grafana/GrafanaDashboard.java
new file mode 100644
index 000000000..f7ab207ff
--- /dev/null
+++
b/common/src/main/java/org/apache/hertzbeat/common/entity/grafana/GrafanaDashboard.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.grafana;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.persistence.Entity;
+import jakarta.persistence.Id;
+import jakarta.persistence.Table;
+import jakarta.persistence.Transient;
+import java.io.Serializable;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+
+
+
+/**
+ * Grafana dashboard entity
+ */
+@Entity
+@Table(name = "hzb_grafana_dashboard")
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+@Schema(description = "Grafana dashboard entity")
+public class GrafanaDashboard implements Serializable {
+ @Schema(description = "Dashboard folderUid")
+ private String folderUid;
+ @Schema(description = "Dashboard slug")
+ private String slug;
+ @Schema(description = "Dashboard status")
+ private String status;
+ @Schema(description = "Dashboard uid")
+ private String uid;
+ @Schema(description = "Dashboard url")
+ private String url;
+ @Schema(description = "Dashboard version")
+ private Long version;
+ @Id
+ @Schema(description = "Monitor id")
+ private Long monitorId;
+ @Schema(description = "is enabled")
+ private boolean enabled;
+ @Schema(description = "template")
+ @Transient
+ private String template;
+}
diff --git
a/common/src/main/java/org/apache/hertzbeat/common/entity/grafana/ServiceAccount.java
b/common/src/main/java/org/apache/hertzbeat/common/entity/grafana/ServiceAccount.java
new file mode 100644
index 000000000..6aee87e07
--- /dev/null
+++
b/common/src/main/java/org/apache/hertzbeat/common/entity/grafana/ServiceAccount.java
@@ -0,0 +1,61 @@
+/*
+ * 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.grafana;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.persistence.Entity;
+import jakarta.persistence.Id;
+import jakarta.persistence.Table;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+
+
+
+/**
+ * Grafana service account entity
+ */
+@Entity
+@Table(name = "hzb_grafana_service_account")
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+@Schema(description = "Grafana service account entity")
+public class ServiceAccount {
+ @Id
+ @Schema(description = "Service account id")
+ private Long id;
+ @Schema(description = "Service account name")
+ private String name;
+ @Schema(description = "Service account role")
+ private String role;
+ @Schema(description = "Service account is disabled")
+ private Boolean isDisabled;
+ @Schema(description = "Service account tokens")
+ private Integer tokens;
+ @Schema(description = "Service account avatar url")
+ private String avatarUrl;
+ @Schema(description = "Service account login")
+ private String login;
+ @Schema(description = "Service account orgId")
+ private Integer orgId;
+
+}
diff --git
a/common/src/main/java/org/apache/hertzbeat/common/entity/grafana/ServiceToken.java
b/common/src/main/java/org/apache/hertzbeat/common/entity/grafana/ServiceToken.java
new file mode 100644
index 000000000..1ffcc9e81
--- /dev/null
+++
b/common/src/main/java/org/apache/hertzbeat/common/entity/grafana/ServiceToken.java
@@ -0,0 +1,50 @@
+/*
+ * 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.grafana;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.Id;
+import jakarta.persistence.Table;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+
+/**
+ * Grafana service token entity
+ */
+@Entity
+@Table(name = "hzb_grafana_service_token")
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+@Schema(description = "Grafana service token entity")
+public class ServiceToken {
+ @Id
+ @Schema(description = "Service token id")
+ private Long id;
+ @Schema(description = "Service token name")
+ private String name;
+ @Schema(description = "Service token key")
+ @Column(name = "`key`")
+ private String key;
+}
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 2d3665bf1..c76051d3f 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
@@ -32,6 +32,7 @@ import jakarta.persistence.JoinColumn;
import jakarta.persistence.JoinTable;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.Table;
+import jakarta.persistence.Transient;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.Size;
@@ -41,6 +42,7 @@ import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
+import org.apache.hertzbeat.common.entity.grafana.GrafanaDashboard;
import org.apache.hertzbeat.common.support.valid.HostValid;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
@@ -166,4 +168,11 @@ public class Monitor {
joinColumns = {@JoinColumn(name = "monitor_id", referencedColumnName =
"id")},
inverseJoinColumns = {@JoinColumn(name = "tag_id",
referencedColumnName = "id")})
private List<Tag> tags;
+
+ /**
+ * grafana dashboard
+ */
+ @Schema(title = "grafana dashboard")
+ @Transient
+ private GrafanaDashboard grafanaDashboard;
}
diff --git a/grafana/pom.xml b/grafana/pom.xml
new file mode 100644
index 000000000..8e5afd8b4
--- /dev/null
+++ b/grafana/pom.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ 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.
+ -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.hertzbeat</groupId>
+ <artifactId>hertzbeat</artifactId>
+ <version>2.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>hertzbeat-grafana</artifactId>
+ <name>${project.artifactId}</name>
+ <properties>
+ <maven-jar-plugin.version>3.2.0</maven-jar-plugin.version>
+ <maven-assembly-plugin.version>3.3.0</maven-assembly-plugin.version>
+ </properties>
+
+ <dependencies>
+ <!-- hertzbeat common -->
+ <dependency>
+ <groupId>org.apache.hertzbeat</groupId>
+ <artifactId>hertzbeat-common</artifactId>
+ </dependency>
+ <!-- hertzbeat warehouse -->
+ <dependency>
+ <groupId>org.apache.hertzbeat</groupId>
+ <artifactId>hertzbeat-warehouse</artifactId>
+ </dependency>
+ <!-- jpa -->
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-data-jpa</artifactId>
+ </dependency>
+ <!-- swagger -->
+ <dependency>
+ <groupId>org.springdoc</groupId>
+ <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
+ </dependency>
+ </dependencies>
+</project>
diff --git
a/grafana/src/main/java/org/apache/hertzbeat/grafana/common/CommonConstants.java
b/grafana/src/main/java/org/apache/hertzbeat/grafana/common/CommonConstants.java
new file mode 100644
index 000000000..7c227d0e5
--- /dev/null
+++
b/grafana/src/main/java/org/apache/hertzbeat/grafana/common/CommonConstants.java
@@ -0,0 +1,77 @@
+/*
+ * 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.grafana.common;
+
+/**
+ * Grafana Common Constants
+ */
+public interface CommonConstants {
+ String HTTP = "http://";
+
+ String HTTPS = "https://";
+
+ String KIOSK = "?kiosk=tv";
+
+ String REFRESH = "&refresh=15s";
+
+ String INSTANCE = "&var-instance=";
+
+ String AUTHORIZATION = "Authorization";
+
+ String BEARER = "Bearer ";
+
+ String CREATE_DASHBOARD_API = "/api/dashboards/db";
+
+ String DELETE_DASHBOARD_API = "/api/dashboards/uid/%s";
+
+ String DATASOURCE_NAME = "hertzbeat-victoria-metrics";
+
+ String DATASOURCE_TYPE = "prometheus";
+
+ String DATASOURCE_ACCESS = "proxy";
+
+ String CREATE_DATASOURCE_API = "%s:%s@%s/api/datasources";
+
+ String DELETE_DATASOURCE_API = "%s:%s@%s/api/datasources/name/%s";
+
+ String ACCOUNT_NAME = "hertzbeat";
+
+ String ACCOUNT_ROLE = "Admin";
+
+ String ACCOUNT_TOKEN_NAME = "hertzbeat-token";
+
+ String CREATE_SERVICE_ACCOUNT_API = "%s:%s@%s/api/serviceaccounts";
+
+ String GET_SERVICE_ACCOUNTS_API = "%s:%s@%s/api/serviceaccounts/search";
+
+ String DELETE_SERVICE_ACCOUNT_API = "%s:%s@%s/api/serviceaccounts/%d";
+
+ String CREATE_SERVICE_TOKEN_API = "%s:%s@%s/api/serviceaccounts/%d/tokens";
+
+ String GET_SERVICE_TOKENS_API = "%s:%s@%s/api/serviceaccounts/%d/tokens";
+
+ String NAME = "name";
+
+ String APPLICATION_JSON = "application/json";
+
+ String HERTZBEAT_TOKEN = "hertzbeat-token";
+
+ String TYPE = "type";
+
+ String URL = "url";
+}
diff --git
a/grafana/src/main/java/org/apache/hertzbeat/grafana/config/GrafanaAutoConfiguration.java
b/grafana/src/main/java/org/apache/hertzbeat/grafana/config/GrafanaAutoConfiguration.java
new file mode 100644
index 000000000..8cf6fd094
--- /dev/null
+++
b/grafana/src/main/java/org/apache/hertzbeat/grafana/config/GrafanaAutoConfiguration.java
@@ -0,0 +1,29 @@
+/*
+ * 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.grafana.config;
+
+import
org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.ComponentScan;
+
+/**
+ * Grafana auto configuration.
+ */
+@ComponentScan(basePackages = "org.apache.hertzbeat.grafana")
+@EnableConfigurationProperties(GrafanaProperties.class)
+public class GrafanaAutoConfiguration {
+}
diff --git
a/grafana/src/main/java/org/apache/hertzbeat/grafana/config/GrafanaInit.java
b/grafana/src/main/java/org/apache/hertzbeat/grafana/config/GrafanaInit.java
new file mode 100644
index 000000000..6306dbeb9
--- /dev/null
+++ b/grafana/src/main/java/org/apache/hertzbeat/grafana/config/GrafanaInit.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.grafana.config;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.hertzbeat.grafana.service.DatasourceService;
+import org.apache.hertzbeat.grafana.service.ServiceAccountService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.CommandLineRunner;
+import org.springframework.stereotype.Component;
+
+/**
+ * grafana init
+ */
+@Component
+@Slf4j
+public class GrafanaInit implements CommandLineRunner {
+ @Autowired
+ private GrafanaProperties grafanaProperties;
+ @Autowired
+ private ServiceAccountService serviceAccountService;
+ @Autowired
+ private DatasourceService datasourceService;
+
+ //1. Determine whether the configuration is filled out completely
+ //2. Determine whether there is an account, if not, create and ensure that
the account is unique
+ //3. Determine whether there is a token, if not, create and ensure that
the account is unique.
+ @Override
+ public void run(String... args) throws Exception {
+ if (grafanaProperties.enabled() && grafanaProperties.url() != null &&
grafanaProperties.username() != null && grafanaProperties.password() != null) {
+ serviceAccountService.reload();
+ try {
+ serviceAccountService.getAccount();
+ } catch (RuntimeException e) {
+ log.error("service account is not exist, create service
account");
+ serviceAccountService.createServiceAccount();
+ }
+ try {
+ serviceAccountService.getToken();
+ } catch (RuntimeException e) {
+ log.error("service token is not exist, create service token");
+ serviceAccountService.createToken();
+ }
+ datasourceService.deleteDatasource();
+ datasourceService.createDatasource();
+ }
+ }
+
+
+}
diff --git
a/grafana/src/main/java/org/apache/hertzbeat/grafana/config/GrafanaProperties.java
b/grafana/src/main/java/org/apache/hertzbeat/grafana/config/GrafanaProperties.java
new file mode 100644
index 000000000..77544bba7
--- /dev/null
+++
b/grafana/src/main/java/org/apache/hertzbeat/grafana/config/GrafanaProperties.java
@@ -0,0 +1,58 @@
+/*
+ * 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.grafana.config;
+
+import static org.apache.hertzbeat.grafana.common.CommonConstants.HTTP;
+import static org.apache.hertzbeat.grafana.common.CommonConstants.HTTPS;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.boot.context.properties.bind.DefaultValue;
+
+/**
+ * grafana configuration
+ */
+@Slf4j
+@ConfigurationProperties(prefix = "grafana")
+public record GrafanaProperties(@DefaultValue("false") boolean enabled,
+ @DefaultValue("http://127.0.0.1:3000") String
url,
+ @DefaultValue("admin") String username,
+ @DefaultValue("admin") String password) {
+ /**
+ * get the prefix of the grafana url, such as http or https
+ */
+ public String getPrefix() {
+ if (url.startsWith(HTTP)) {
+ return HTTP;
+ } else if (url.startsWith(HTTPS)) {
+ return HTTPS;
+ }
+ return HTTP;
+ }
+
+ /**
+ * get the grafana url without the prefix, such as localhost:3000
+ */
+ public String getUrl() {
+ if (getPrefix().equals(HTTP)) {
+ return url.replace(HTTP, "");
+ } else if (getPrefix().equals(HTTPS)) {
+ return url.replace(HTTPS, "");
+ }
+ return url;
+ }
+}
diff --git
a/grafana/src/main/java/org/apache/hertzbeat/grafana/controller/DashboardController.java
b/grafana/src/main/java/org/apache/hertzbeat/grafana/controller/DashboardController.java
new file mode 100644
index 000000000..754eb0361
--- /dev/null
+++
b/grafana/src/main/java/org/apache/hertzbeat/grafana/controller/DashboardController.java
@@ -0,0 +1,111 @@
+/*
+ * 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.grafana.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.tags.Tag;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.hertzbeat.common.entity.dto.Message;
+import org.apache.hertzbeat.common.entity.grafana.GrafanaDashboard;
+import org.apache.hertzbeat.grafana.config.GrafanaProperties;
+import org.apache.hertzbeat.grafana.service.DashboardService;
+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.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * Controller for managing Grafana dashboards via API.
+ */
+@Slf4j
+@Tag(name = "Dashboard API")
+@RestController
+@RequestMapping(path = "/api/grafana/dashboard", produces =
{APPLICATION_JSON_VALUE})
+public class DashboardController {
+
+ @Autowired
+ private DashboardService dashboardService;
+
+ @Autowired
+ private GrafanaProperties grafanaProperties;
+
+ /**
+ * Creates a new Grafana dashboard.
+ *
+ * @param dashboardJson JSON representation of the dashboard
+ * @param monitorId the ID of the monitor associated with the dashboard
+ * @return ResponseEntity containing success or failure message
+ */
+ @Operation(summary = "Create dashboard", description = "Create dashboard")
+ @PostMapping
+ public ResponseEntity<Message<?>> createDashboard(@RequestParam String
dashboardJson, @RequestParam Long monitorId) {
+ try {
+ ResponseEntity<?> response =
dashboardService.createDashboard(dashboardJson, monitorId);
+ if (response.getStatusCode().is2xxSuccessful()) {
+ return ResponseEntity.ok(Message.success("create dashboard
success"));
+ }
+ } catch (Exception e) {
+ log.error("create dashboard error", e);
+ return ResponseEntity.ok(Message.fail(FAIL_CODE, e.getMessage()));
+ }
+ return ResponseEntity.ok(Message.fail(FAIL_CODE, "create dashboard
fail"));
+ }
+
+ /**
+ * Retrieves a Grafana dashboard by monitor ID.
+ *
+ * @param monitorId the ID of the monitor associated with the dashboard
+ * @return ResponseEntity containing the GrafanaDashboard object or
failure message
+ */
+ @Operation(summary = "Get dashboard by monitor id", description = "Get
dashboard by monitor id")
+ @GetMapping
+ public ResponseEntity<Message<?>> getDashboardByMonitorId(@RequestParam
Long monitorId) {
+ try {
+ GrafanaDashboard grafanaDashboard =
dashboardService.getDashboardByMonitorId(monitorId);
+ return ResponseEntity.ok(Message.success(grafanaDashboard));
+ } catch (Exception e) {
+ log.error("get dashboard error", e);
+ return ResponseEntity.ok(Message.fail(FAIL_CODE, "get dashboard
fail"));
+ }
+ }
+
+ /**
+ * Deletes a Grafana dashboard by monitor ID.
+ *
+ * @param monitorId the ID of the monitor associated with the dashboard
+ * @return ResponseEntity containing success or failure message
+ */
+ @Operation(summary = "Delete dashboard by monitor id", description =
"Delete dashboard by monitor id")
+ @DeleteMapping
+ public ResponseEntity<Message<String>>
deleteDashboardByMonitorId(@RequestParam Long monitorId) {
+ try {
+ dashboardService.deleteDashboard(monitorId);
+ } catch (Exception e) {
+ log.error("delete dashboard error", e);
+ return ResponseEntity.ok(Message.fail(FAIL_CODE, "delete dashboard
fail"));
+ }
+ return ResponseEntity.ok(Message.fail(FAIL_CODE, "delete dashboard
fail"));
+ }
+}
diff --git
a/grafana/src/main/java/org/apache/hertzbeat/grafana/controller/ServiceAccountController.java
b/grafana/src/main/java/org/apache/hertzbeat/grafana/controller/ServiceAccountController.java
new file mode 100644
index 000000000..983fab1e3
--- /dev/null
+++
b/grafana/src/main/java/org/apache/hertzbeat/grafana/controller/ServiceAccountController.java
@@ -0,0 +1,119 @@
+/*
+ * 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.grafana.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.tags.Tag;
+import org.apache.hertzbeat.common.entity.dto.Message;
+import org.apache.hertzbeat.grafana.service.ServiceAccountService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * Controller for managing Grafana service accounts and tokens via API.
+ */
+@Tag(name = "Service Account API")
+@RestController
+@RequestMapping(path = "/api/grafana/service-account", produces =
{APPLICATION_JSON_VALUE})
+public class ServiceAccountController {
+
+ @Autowired
+ private ServiceAccountService serviceAccountService;
+
+ /**
+ * Creates a new service admin account.
+ *
+ * @return ResponseEntity containing the result of the account creation
+ */
+ @PostMapping(path = "/account")
+ @Operation(summary = "Create service account", description = "Create
service account")
+ public ResponseEntity<Message<?>> createServiceAccount() {
+ try {
+ ResponseEntity<String> response =
serviceAccountService.createServiceAccount();
+ return handleResponse(response);
+ } catch (Exception e) {
+ return handleException(e);
+ }
+ }
+
+ /**
+ * Retrieves all service accounts.
+ *
+ * @return ResponseEntity containing the list of service accounts
+ */
+ @GetMapping(path = "/accounts")
+ @Operation(summary = "Get service account", description = "Get service
account")
+ public ResponseEntity<Message<?>> getServiceAccount() {
+ try {
+ ResponseEntity<String> response =
serviceAccountService.getAccounts();
+ return handleResponse(response);
+ } catch (Exception e) {
+ return handleException(e);
+ }
+ }
+
+ /**
+ * Creates a new API token for a service account.
+ *
+ * @return ResponseEntity containing the result of the token creation
+ */
+ @PostMapping(path = "/token")
+ @Operation(summary = "Create service account token", description = "Create
service account token")
+ public ResponseEntity<Message<?>> createToken() {
+ try {
+ ResponseEntity<String> response =
serviceAccountService.createToken();
+ return handleResponse(response);
+ } catch (Exception e) {
+ return handleException(e);
+ }
+ }
+
+ /**
+ * Retrieves all API tokens for service accounts.
+ *
+ * @return ResponseEntity containing the list of tokens
+ */
+ @GetMapping(path = "/tokens")
+ @Operation(summary = "Get service account token", description = "Get
service account token")
+ public ResponseEntity<Message<?>> getToken() {
+ try {
+ ResponseEntity<String> response =
serviceAccountService.getTokens();
+ return handleResponse(response);
+ } catch (Exception e) {
+ return handleException(e);
+ }
+ }
+
+ private ResponseEntity<Message<?>> handleResponse(ResponseEntity<String>
response) {
+ if (response.getStatusCode().is2xxSuccessful()) {
+ return ResponseEntity.ok(Message.success(response.getBody()));
+ }
+ return ResponseEntity.ok(Message.fail(FAIL_CODE, response.getBody()));
+ }
+
+ private ResponseEntity<Message<?>> handleException(Exception e) {
+ return ResponseEntity.ok(Message.fail(FAIL_CODE, e.getMessage()));
+ }
+}
diff --git
a/grafana/src/main/java/org/apache/hertzbeat/grafana/dao/DashboardDao.java
b/grafana/src/main/java/org/apache/hertzbeat/grafana/dao/DashboardDao.java
new file mode 100644
index 000000000..126cab796
--- /dev/null
+++ b/grafana/src/main/java/org/apache/hertzbeat/grafana/dao/DashboardDao.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hertzbeat.grafana.dao;
+
+import java.util.List;
+import org.apache.hertzbeat.common.entity.grafana.GrafanaDashboard;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+/**
+ * Dashboard Dao
+ */
+public interface DashboardDao extends JpaRepository<GrafanaDashboard, Long>,
JpaSpecificationExecutor<GrafanaDashboard> {
+ /**
+ * find by monitor id
+ * @param monitorId monitor id
+ * @return dashboard
+ */
+ GrafanaDashboard findByMonitorId(Long monitorId);
+
+ /**
+ * delete by monitor id
+ * @param monitorId monitor id
+ */
+ void deleteByMonitorId(Long monitorId);
+
+ /**
+ * find by dashboard uid
+ * @param dashboardUid dashboard uid
+ * @return dashboard
+ */
+ List<GrafanaDashboard> findByUid(String dashboardUid);
+}
diff --git
a/grafana/src/main/java/org/apache/hertzbeat/grafana/dao/ServiceAccountDao.java
b/grafana/src/main/java/org/apache/hertzbeat/grafana/dao/ServiceAccountDao.java
new file mode 100644
index 000000000..ec47f495f
--- /dev/null
+++
b/grafana/src/main/java/org/apache/hertzbeat/grafana/dao/ServiceAccountDao.java
@@ -0,0 +1,38 @@
+/*
+ * 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.grafana.dao;
+
+import org.apache.hertzbeat.common.entity.grafana.ServiceAccount;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.data.jpa.repository.Modifying;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * ServiceAccount Dao
+ */
+public interface ServiceAccountDao extends JpaRepository<ServiceAccount,
Long>, JpaSpecificationExecutor<ServiceAccount> {
+
+ ServiceAccount findByName(String name);
+
+ @Transactional
+ @Modifying
+ @Query(value = "truncate table hzb_grafana_service_account", nativeQuery =
true)
+ void truncate();
+}
diff --git
a/grafana/src/main/java/org/apache/hertzbeat/grafana/dao/ServiceTokenDao.java
b/grafana/src/main/java/org/apache/hertzbeat/grafana/dao/ServiceTokenDao.java
new file mode 100644
index 000000000..bb7d67e56
--- /dev/null
+++
b/grafana/src/main/java/org/apache/hertzbeat/grafana/dao/ServiceTokenDao.java
@@ -0,0 +1,37 @@
+/*
+ * 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.grafana.dao;
+
+import org.apache.hertzbeat.common.entity.grafana.ServiceToken;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.data.jpa.repository.Modifying;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * ServiceAccount Dao
+ */
+public interface ServiceTokenDao extends JpaRepository<ServiceToken, Long>,
JpaSpecificationExecutor<ServiceToken> {
+ ServiceToken findByName(String name);
+
+ @Transactional
+ @Modifying
+ @Query(value = "truncate table hzb_grafana_service_token", nativeQuery =
true)
+ void truncate();
+}
diff --git
a/grafana/src/main/java/org/apache/hertzbeat/grafana/service/DashboardService.java
b/grafana/src/main/java/org/apache/hertzbeat/grafana/service/DashboardService.java
new file mode 100644
index 000000000..2249743f2
--- /dev/null
+++
b/grafana/src/main/java/org/apache/hertzbeat/grafana/service/DashboardService.java
@@ -0,0 +1,181 @@
+/*
+ * 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.grafana.service;
+
+import static
org.apache.hertzbeat.grafana.common.CommonConstants.CREATE_DASHBOARD_API;
+import static
org.apache.hertzbeat.grafana.common.CommonConstants.DELETE_DASHBOARD_API;
+import static org.apache.hertzbeat.grafana.common.CommonConstants.INSTANCE;
+import static org.apache.hertzbeat.grafana.common.CommonConstants.KIOSK;
+import static org.apache.hertzbeat.grafana.common.CommonConstants.REFRESH;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.hertzbeat.common.entity.grafana.GrafanaDashboard;
+import org.apache.hertzbeat.common.util.JsonUtil;
+import org.apache.hertzbeat.grafana.config.GrafanaProperties;
+import org.apache.hertzbeat.grafana.dao.DashboardDao;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.client.RestTemplate;
+import lombok.extern.slf4j.Slf4j;
+
+
+/**
+ * Service for managing Grafana dashboards.
+ */
+@Service
+@Slf4j
+public class DashboardService {
+
+ @Autowired
+ private ServiceAccountService serviceAccountService;
+
+ @Autowired
+ private DashboardDao dashboardDao;
+
+ @Autowired
+ private GrafanaProperties grafanaProperties;
+
+ @Autowired
+ private RestTemplate restTemplate;
+
+ /**
+ * Creates a new dashboard in Grafana.
+ *
+ * @param dashboardJson the JSON representation of the dashboard
+ * @param monitorId the ID of the monitor associated with the dashboard
+ * @return ResponseEntity containing the response from Grafana
+ */
+ @Transactional(rollbackFor = Exception.class)
+ public ResponseEntity<?> createDashboard(String dashboardJson, Long
monitorId) {
+ String token = serviceAccountService.getToken();
+ String url = grafanaProperties.getPrefix() +
grafanaProperties.getUrl() + CREATE_DASHBOARD_API;
+
+ HttpHeaders headers = new HttpHeaders();
+ headers.setContentType(MediaType.APPLICATION_JSON);
+ headers.setBearerAuth(token);
+
+ Map<String, Object> body = new HashMap<>();
+ body.put("dashboard", JsonUtil.fromJson(dashboardJson, Object.class));
+ body.put("overwrite", true);
+
+ HttpEntity<Map<String, Object>> requestEntity = new HttpEntity<>(body,
headers);
+
+ try {
+ ResponseEntity<String> response = restTemplate.postForEntity(url,
requestEntity, String.class);
+
+ if (response.getStatusCode().is2xxSuccessful()) {
+ GrafanaDashboard grafanaDashboard =
JsonUtil.fromJson(response.getBody(), GrafanaDashboard.class);
+ if (grafanaDashboard != null) {
+ grafanaDashboard.setEnabled(true);
+ grafanaDashboard.setUrl(grafanaProperties.getPrefix() +
grafanaProperties.getUrl()
+ +
grafanaDashboard.getUrl().replace(grafanaProperties.getUrl(), "") + KIOSK +
REFRESH + INSTANCE + monitorId);
+ grafanaDashboard.setMonitorId(monitorId);
+ dashboardDao.save(grafanaDashboard);
+ log.info("create dashboard success, token: {}",
response.getBody());
+ }
+ return response;
+ } else {
+ log.error("create dashboard error: {}",
response.getStatusCode());
+ throw new RuntimeException("create dashboard error");
+ }
+ } catch (Exception ex) {
+ log.error("create dashboard error", ex);
+ throw new RuntimeException("create dashboard error", ex);
+ }
+ }
+
+
+ /**
+ * Deletes a dashboard in Grafana by monitor ID.
+ *
+ * @param monitorId the ID of the monitor associated with the dashboard
+ */
+ @Transactional(rollbackFor = Exception.class)
+ public void deleteDashboard(Long monitorId) {
+ GrafanaDashboard grafanaDashboard =
dashboardDao.findByMonitorId(monitorId);
+ String uid = grafanaDashboard.getUid();
+ List<GrafanaDashboard> grafanaDashboards = dashboardDao.findByUid(uid);
+
+ if (grafanaDashboards.size() > 1) {
+ dashboardDao.deleteByMonitorId(monitorId);
+ } else {
+ String token = serviceAccountService.getToken();
+ String url = grafanaProperties.getPrefix() +
grafanaProperties.getUrl() + String.format(DELETE_DASHBOARD_API, uid);
+
+ HttpHeaders headers = new HttpHeaders();
+ headers.setContentType(MediaType.APPLICATION_JSON);
+ headers.setBearerAuth(token);
+
+ HttpEntity<Void> requestEntity = new HttpEntity<>(headers);
+ dashboardDao.deleteByMonitorId(monitorId);
+
+ ResponseEntity<String> response = restTemplate.exchange(url,
HttpMethod.DELETE, requestEntity, String.class);
+
+ if (response.getStatusCode().is2xxSuccessful()) {
+ log.info("delete dashboard success");
+ } else {
+ log.error("delete dashboard error: {}",
response.getStatusCode());
+ throw new RuntimeException("delete dashboard error");
+ }
+ }
+ }
+
+
+ /**
+ * Retrieves a dashboard by monitor ID.
+ *
+ * @param monitorId the ID of the monitor associated with the dashboard
+ * @return GrafanaDashboard object
+ */
+ public GrafanaDashboard getDashboardByMonitorId(Long monitorId) {
+ return dashboardDao.findByMonitorId(monitorId);
+ }
+
+ /**
+ * Disables a Grafana dashboard by monitor ID.
+ *
+ * @param monitorId the ID of the monitor associated with the dashboard
+ */
+ public void closeGrafanaDashboard(Long monitorId) {
+ GrafanaDashboard grafanaDashboard =
dashboardDao.findByMonitorId(monitorId);
+ if (grafanaDashboard != null) {
+ grafanaDashboard.setEnabled(false);
+ dashboardDao.save(grafanaDashboard);
+ }
+ }
+
+ /**
+ * Enables a Grafana dashboard by monitor ID.
+ *
+ * @param monitorId the ID of the monitor associated with the dashboard
+ */
+ public void openGrafanaDashboard(Long monitorId) {
+ GrafanaDashboard grafanaDashboard =
dashboardDao.findByMonitorId(monitorId);
+ if (grafanaDashboard != null) {
+ grafanaDashboard.setEnabled(true);
+ dashboardDao.save(grafanaDashboard);
+ }
+ }
+}
diff --git
a/grafana/src/main/java/org/apache/hertzbeat/grafana/service/DatasourceService.java
b/grafana/src/main/java/org/apache/hertzbeat/grafana/service/DatasourceService.java
new file mode 100644
index 000000000..baebd1baf
--- /dev/null
+++
b/grafana/src/main/java/org/apache/hertzbeat/grafana/service/DatasourceService.java
@@ -0,0 +1,134 @@
+/*
+ * 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.grafana.service;
+
+
+import static
org.apache.hertzbeat.grafana.common.CommonConstants.CREATE_DATASOURCE_API;
+import static
org.apache.hertzbeat.grafana.common.CommonConstants.DATASOURCE_ACCESS;
+import static
org.apache.hertzbeat.grafana.common.CommonConstants.DATASOURCE_NAME;
+import static
org.apache.hertzbeat.grafana.common.CommonConstants.DATASOURCE_TYPE;
+import static
org.apache.hertzbeat.grafana.common.CommonConstants.DELETE_DATASOURCE_API;
+import jakarta.annotation.PostConstruct;
+import java.util.Base64;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.hertzbeat.grafana.config.GrafanaProperties;
+import
org.apache.hertzbeat.warehouse.store.history.vm.VictoriaMetricsProperties;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.RestTemplate;
+
+/**
+ * Service for managing Grafana datasources.
+ */
+@Service
+@Slf4j
+public class DatasourceService {
+
+ private String grafanaUrl;
+ private String username;
+ private String password;
+ private String prefix;
+ private String victoriaMetricsUrl;
+
+ private final GrafanaProperties grafanaProperties;
+ private final VictoriaMetricsProperties warehouseProperties;
+ private final RestTemplate restTemplate;
+
+ @Autowired
+ public DatasourceService(
+ GrafanaProperties grafanaProperties,
+ VictoriaMetricsProperties warehouseProperties,
+ RestTemplate restTemplate
+ ) {
+ this.grafanaProperties = grafanaProperties;
+ this.warehouseProperties = warehouseProperties;
+ this.restTemplate = restTemplate;
+ }
+
+ @PostConstruct
+ public void init() {
+ this.grafanaUrl = grafanaProperties.getUrl();
+ this.username = grafanaProperties.username();
+ this.password = grafanaProperties.password();
+ this.prefix = grafanaProperties.getPrefix();
+ this.victoriaMetricsUrl = warehouseProperties.url();
+ }
+
+ /**
+ * Create a new datasource in Grafana.
+ */
+ public void createDatasource() {
+ String url = String.format(prefix + CREATE_DATASOURCE_API, username,
password, grafanaUrl);
+
+ HttpHeaders headers = createHeaders();
+
+ String body = String.format(
+
"{\"name\":\"%s\",\"type\":\"%s\",\"access\":\"%s\",\"url\":\"%s\",\"basicAuth\":%s}",
+ DATASOURCE_NAME, DATASOURCE_TYPE, DATASOURCE_ACCESS,
victoriaMetricsUrl, false
+ );
+
+ HttpEntity<String> entity = new HttpEntity<>(body, headers);
+
+ try {
+ ResponseEntity<String> response = restTemplate.postForEntity(url,
entity, String.class);
+ if (response.getStatusCode().is2xxSuccessful()) {
+ log.info("Create datasource success");
+ }
+ } catch (Exception ex) {
+ log.error("Create datasource error", ex);
+ throw new RuntimeException("Create datasource error", ex);
+ }
+ }
+
+ /**
+ * Delete a datasource in Grafana.
+ */
+ public void deleteDatasource() {
+ String url = String.format(prefix + DELETE_DATASOURCE_API, username,
password, grafanaUrl, DATASOURCE_NAME);
+
+ HttpHeaders headers = createHeaders();
+
+ HttpEntity<Void> entity = new HttpEntity<>(headers);
+
+ try {
+ ResponseEntity<String> response = restTemplate.exchange(url,
HttpMethod.DELETE, entity, String.class);
+ if (response.getStatusCode().is2xxSuccessful()) {
+ log.info("Delete datasource success");
+ }
+
+ } catch (Exception ex) {
+ log.error("Delete datasource error", ex);
+ }
+ }
+
+ private HttpHeaders createHeaders() {
+ String auth = username + ":" + password;
+ byte[] encodedAuth = Base64.getEncoder().encode(auth.getBytes());
+ String authHeader = "Basic " + new String(encodedAuth);
+
+ HttpHeaders headers = new HttpHeaders();
+ headers.setContentType(MediaType.APPLICATION_JSON);
+ headers.set("Authorization", authHeader);
+ return headers;
+ }
+}
diff --git
a/grafana/src/main/java/org/apache/hertzbeat/grafana/service/ServiceAccountService.java
b/grafana/src/main/java/org/apache/hertzbeat/grafana/service/ServiceAccountService.java
new file mode 100644
index 000000000..355c621b9
--- /dev/null
+++
b/grafana/src/main/java/org/apache/hertzbeat/grafana/service/ServiceAccountService.java
@@ -0,0 +1,309 @@
+/*
+ * 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.grafana.service;
+
+import static org.apache.hertzbeat.grafana.common.CommonConstants.ACCOUNT_NAME;
+import static org.apache.hertzbeat.grafana.common.CommonConstants.ACCOUNT_ROLE;
+import static
org.apache.hertzbeat.grafana.common.CommonConstants.ACCOUNT_TOKEN_NAME;
+import static
org.apache.hertzbeat.grafana.common.CommonConstants.CREATE_SERVICE_ACCOUNT_API;
+import static
org.apache.hertzbeat.grafana.common.CommonConstants.CREATE_SERVICE_TOKEN_API;
+import static
org.apache.hertzbeat.grafana.common.CommonConstants.DELETE_SERVICE_ACCOUNT_API;
+import static
org.apache.hertzbeat.grafana.common.CommonConstants.GET_SERVICE_ACCOUNTS_API;
+import static
org.apache.hertzbeat.grafana.common.CommonConstants.GET_SERVICE_TOKENS_API;
+import static
org.apache.hertzbeat.grafana.common.CommonConstants.HERTZBEAT_TOKEN;
+import com.fasterxml.jackson.databind.JsonNode;
+import jakarta.annotation.PostConstruct;
+import java.util.Base64;
+import java.util.List;
+import java.util.Objects;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.hertzbeat.common.entity.grafana.ServiceAccount;
+import org.apache.hertzbeat.common.entity.grafana.ServiceToken;
+import org.apache.hertzbeat.common.util.JsonUtil;
+import org.apache.hertzbeat.grafana.config.GrafanaProperties;
+import org.apache.hertzbeat.grafana.dao.ServiceAccountDao;
+import org.apache.hertzbeat.grafana.dao.ServiceTokenDao;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.RestTemplate;
+
+
+/**
+ * Service for managing Grafana service accounts and tokens.
+ */
+@Service
+@Slf4j
+public class ServiceAccountService {
+
+ private final GrafanaProperties grafanaProperties;
+ private final ServiceAccountDao serviceAccountDao;
+ private final ServiceTokenDao serviceTokenDao;
+ private final RestTemplate restTemplate;
+
+ private String url;
+ private String username;
+ private String password;
+ private String prefix;
+
+ @Autowired
+ public ServiceAccountService(
+ GrafanaProperties grafanaProperties,
+ ServiceAccountDao serviceAccountDao,
+ ServiceTokenDao serviceTokenDao,
+ RestTemplate restTemplate
+ ) {
+ this.grafanaProperties = grafanaProperties;
+ this.serviceAccountDao = serviceAccountDao;
+ this.serviceTokenDao = serviceTokenDao;
+ this.restTemplate = restTemplate;
+ }
+
+ @PostConstruct
+ public void init() {
+ this.url = grafanaProperties.getUrl();
+ this.username = grafanaProperties.username();
+ this.password = grafanaProperties.password();
+ this.prefix = grafanaProperties.getPrefix();
+ ServiceToken serviceToken =
serviceTokenDao.findByName(ACCOUNT_TOKEN_NAME);
+ if (serviceToken == null) {
+ log.error("Service token {} not found", ACCOUNT_TOKEN_NAME);
+ }
+ }
+
+ /**
+ * Creates a new service admin account.
+ *
+ * @return ResponseEntity containing the result of the account creation
+ */
+ public ResponseEntity<String> createServiceAccount() {
+ String endpoint = String.format(prefix + CREATE_SERVICE_ACCOUNT_API,
username, password, url);
+ HttpHeaders headers = createHeaders();
+ String body =
String.format("{\"name\":\"%s\",\"role\":\"%s\",\"isDisabled\":false}",
ACCOUNT_NAME, ACCOUNT_ROLE);
+
+ HttpEntity<String> request = new HttpEntity<>(body, headers);
+ try {
+ ResponseEntity<String> response =
restTemplate.postForEntity(endpoint, request, String.class);
+ if (response.getStatusCode().is2xxSuccessful()) {
+ ServiceAccount serviceAccount =
JsonUtil.fromJson(response.getBody(), ServiceAccount.class);
+ if (serviceAccount != null) {
+ serviceAccountDao.save(serviceAccount);
+ log.info("Create service account success: {}",
serviceAccount);
+ }
+ }
+ return response;
+ } catch (Exception e) {
+ log.error("Service account creation failed", e);
+ throw new RuntimeException("Service account creation failed");
+ }
+ }
+
+ /**
+ * Deletes a service account by ID.
+ *
+ * @param id ID of the service account to delete
+ * @return ResponseEntity containing the result of the deletion
+ */
+ public ResponseEntity<String> deleteAccount(Long id) {
+ String endpoint = String.format(prefix + DELETE_SERVICE_ACCOUNT_API,
username, password, url, id);
+ HttpHeaders headers = createHeaders();
+
+ HttpEntity<String> request = new HttpEntity<>(headers);
+ try {
+ ResponseEntity<String> response = restTemplate.exchange(endpoint,
HttpMethod.DELETE, request, String.class);
+ if (response.getStatusCode().is2xxSuccessful()) {
+ log.info("Delete service account success");
+ }
+ return response;
+ } catch (Exception e) {
+ log.error("Delete service account error", e);
+ throw new RuntimeException("Delete service account error");
+ }
+ }
+
+ /**
+ * Creates a new API token for a service account.
+ *
+ * @return ResponseEntity containing the result of the token creation
+ */
+ public ResponseEntity<String> createToken() {
+ ServiceAccount hertzbeat = serviceAccountDao.findByName(ACCOUNT_NAME);
+ if (hertzbeat == null) {
+ log.error("Service account not found");
+ throw new RuntimeException("Service account not found");
+ }
+ String endpoint = String.format(prefix + CREATE_SERVICE_TOKEN_API,
username, password, url, hertzbeat.getId());
+ HttpHeaders headers = createHeaders();
+ String body = String.format("{\"name\":\"%s\"}", HERTZBEAT_TOKEN);
+
+ HttpEntity<String> request = new HttpEntity<>(body, headers);
+ try {
+ ResponseEntity<String> response =
restTemplate.postForEntity(endpoint, request, String.class);
+ if (response.getStatusCode().is2xxSuccessful()) {
+ ServiceToken serviceToken =
JsonUtil.fromJson(response.getBody(), ServiceToken.class);
+ if (serviceToken != null) {
+ serviceTokenDao.save(serviceToken);
+ }
+ log.info("Create token success: {}", response.getBody());
+ }
+ return response;
+ } catch (Exception e) {
+ log.error("Create token error", e);
+ throw new RuntimeException("Create token error");
+ }
+ }
+
+ /**
+ * Retrieves all service accounts.
+ *
+ * @return ResponseEntity containing the list of service accounts
+ */
+ public ResponseEntity<String> getAccounts() {
+ String endpoint = String.format(prefix + GET_SERVICE_ACCOUNTS_API,
username, password, url);
+ HttpHeaders headers = createHeaders();
+
+ HttpEntity<String> request = new HttpEntity<>(headers);
+ try {
+ ResponseEntity<String> response = restTemplate.exchange(endpoint,
HttpMethod.GET, request, String.class);
+ if (response.getStatusCode().is2xxSuccessful()) {
+ log.info("Get accounts success");
+ }
+ return response;
+ } catch (Exception e) {
+ log.error("Get accounts error", e);
+ throw new RuntimeException("Get accounts error");
+ }
+ }
+
+ /**
+ * Retrieves all API tokens for service accounts.
+ *
+ * @return ResponseEntity containing the list of tokens
+ */
+ public ResponseEntity<String> getTokens() {
+ String endpoint = String.format(prefix + GET_SERVICE_TOKENS_API,
username, password, url, getAccountId());
+ HttpHeaders headers = createHeaders();
+
+ HttpEntity<String> request = new HttpEntity<>(headers);
+ try {
+ ResponseEntity<String> response = restTemplate.exchange(endpoint,
HttpMethod.GET, request, String.class);
+ if (response.getStatusCode().is2xxSuccessful()) {
+ log.info("Get tokens success");
+ }
+ return response;
+ } catch (Exception e) {
+ log.error("Get tokens error", e);
+ throw new RuntimeException("Get tokens error");
+ }
+ }
+
+ /**
+ * Retrieves the token for a service account.
+ *
+ * @return The token key
+ */
+ public String getToken() {
+ ServiceToken hertzbeatToken =
serviceTokenDao.findByName(ACCOUNT_TOKEN_NAME);
+ if (hertzbeatToken == null) {
+ log.error("Service token not found");
+ throw new RuntimeException("Service token not found");
+ }
+ return hertzbeatToken.getKey();
+ }
+
+ /**
+ * Deletes a service account token.
+ */
+ public void deleteToken() {
+ ServiceToken hertzbeatToken =
serviceTokenDao.findByName(ACCOUNT_TOKEN_NAME);
+ if (hertzbeatToken == null) {
+ log.error("Service token not found");
+ throw new RuntimeException("Service token not found");
+ }
+ serviceTokenDao.delete(hertzbeatToken);
+ }
+
+ /**
+ * Retrieves the ID of the service account.
+ *
+ * @return The ID of the service account
+ */
+ public long getAccountId() {
+ ServiceAccount hertzbeat = serviceAccountDao.findByName(ACCOUNT_NAME);
+ if (hertzbeat == null) {
+ log.error("Service account not found");
+ throw new RuntimeException("Service account not found");
+ }
+ log.info("Service account: {}", hertzbeat);
+ return hertzbeat.getId();
+ }
+
+ /**
+ * Retrieves the service account.
+ *
+ * @return The service account
+ */
+ public ServiceAccount getAccount() {
+ ServiceAccount hertzbeat = serviceAccountDao.findByName(ACCOUNT_NAME);
+ if (hertzbeat == null) {
+ log.error("Service account not found");
+ throw new RuntimeException("Service account not found");
+ }
+ log.info("Service account: {}", hertzbeat);
+ return hertzbeat;
+ }
+
+ /**
+ * Deletes the service account.
+ */
+ public void deleteAccount() {
+ ServiceAccount hertzbeat = serviceAccountDao.findByName(ACCOUNT_NAME);
+ if (hertzbeat == null) {
+ log.error("Service account not found");
+ throw new RuntimeException("Service account not found");
+ }
+ serviceAccountDao.delete(hertzbeat);
+ }
+
+ /**
+ * Reloads the service accounts and tokens, clearing existing data.
+ */
+ public void reload() {
+ List<JsonNode> idList =
Objects.requireNonNull(JsonUtil.fromJson(getAccounts().getBody())).path("serviceAccounts").findValues("id");
+ for (JsonNode jsonNode : idList) {
+ deleteAccount(jsonNode.asLong());
+ }
+ serviceAccountDao.truncate();
+ serviceTokenDao.truncate();
+ }
+
+ private HttpHeaders createHeaders() {
+ String auth = username + ":" + password;
+ byte[] encodedAuth = Base64.getEncoder().encode(auth.getBytes());
+ String authHeader = "Basic " + new String(encodedAuth);
+
+ HttpHeaders headers = new HttpHeaders();
+ headers.setContentType(MediaType.APPLICATION_JSON);
+ headers.set("Authorization", authHeader);
+ return headers;
+ }
+}
\ No newline at end of file
diff --git a/grafana/src/main/resources/META-INF/spring.factories
b/grafana/src/main/resources/META-INF/spring.factories
new file mode 100644
index 000000000..d621ea111
--- /dev/null
+++ b/grafana/src/main/resources/META-INF/spring.factories
@@ -0,0 +1,17 @@
+# 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.
+
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+org.apache.hertzbeat.grafana.config.GrafanaAutoConfiguration
\ No newline at end of file
diff --git a/manager/pom.xml b/manager/pom.xml
index 34debeb78..63999336b 100644
--- a/manager/pom.xml
+++ b/manager/pom.xml
@@ -79,6 +79,11 @@
<groupId>org.apache.hertzbeat</groupId>
<artifactId>hertzbeat-plugin</artifactId>
</dependency>
+ <!-- grafana -->
+ <dependency>
+ <groupId>org.apache.hertzbeat</groupId>
+ <artifactId>hertzbeat-grafana</artifactId>
+ </dependency>
<!-- spring -->
<dependency>
<groupId>org.springframework.boot</groupId>
diff --git
a/manager/src/main/java/org/apache/hertzbeat/manager/service/impl/MonitorServiceImpl.java
b/manager/src/main/java/org/apache/hertzbeat/manager/service/impl/MonitorServiceImpl.java
index cd0ea218b..c11b708ee 100644
---
a/manager/src/main/java/org/apache/hertzbeat/manager/service/impl/MonitorServiceImpl.java
+++
b/manager/src/main/java/org/apache/hertzbeat/manager/service/impl/MonitorServiceImpl.java
@@ -61,6 +61,7 @@ import
org.apache.hertzbeat.common.util.IntervalExpressionUtil;
import org.apache.hertzbeat.common.util.IpDomainUtil;
import org.apache.hertzbeat.common.util.JsonUtil;
import org.apache.hertzbeat.common.util.SnowFlakeIdGenerator;
+import org.apache.hertzbeat.grafana.service.DashboardService;
import org.apache.hertzbeat.manager.dao.CollectorDao;
import org.apache.hertzbeat.manager.dao.CollectorMonitorBindDao;
import org.apache.hertzbeat.manager.dao.MonitorDao;
@@ -141,6 +142,9 @@ public class MonitorServiceImpl implements MonitorService {
@Autowired
private WarehouseService warehouseService;
+ @Autowired
+ private DashboardService dashboardService;
+
private final Map<String, ImExportService> imExportServiceMap = new
HashMap<>();
public MonitorServiceImpl(List<ImExportService> imExportServiceList) {
@@ -231,6 +235,10 @@ public class MonitorServiceImpl implements MonitorService {
}
monitor.setId(monitorId);
monitor.setJobId(jobId);
+ // create grafana dashboard
+ if (monitor.getApp().equals(CommonConstants.PROMETHEUS) &&
monitor.getGrafanaDashboard().isEnabled()) {
+
dashboardService.createDashboard(monitor.getGrafanaDashboard().getTemplate(),
monitorId);
+ }
monitorDao.save(monitor);
paramDao.saveAll(params);
} catch (Exception e) {
@@ -564,6 +572,18 @@ public class MonitorServiceImpl implements MonitorService {
}
// force update gmtUpdate time, due the case: monitor not change,
param change. we also think monitor change
monitor.setGmtUpdate(LocalDateTime.now());
+ // create or open grafana dashboard
+ if (monitor.getApp().equals(CommonConstants.PROMETHEUS) &&
monitor.getGrafanaDashboard().isEnabled()) {
+ if (dashboardService.getDashboardByMonitorId(monitorId) ==
null) {
+
dashboardService.createDashboard(monitor.getGrafanaDashboard().getTemplate(),
monitorId);
+ } else {
+ dashboardService.openGrafanaDashboard(monitorId);
+ }
+ }
+ // close grafana dashboard
+ if (monitor.getApp().equals(CommonConstants.PROMETHEUS) &&
!monitor.getGrafanaDashboard().isEnabled()) {
+ dashboardService.closeGrafanaDashboard(monitorId);
+ }
monitorDao.save(monitor);
if (params != null) {
paramDao.saveAll(params);
@@ -621,13 +641,13 @@ public class MonitorServiceImpl implements MonitorService
{
if (monitorOptional.isPresent()) {
Monitor monitor = monitorOptional.get();
MonitorDto monitorDto = new MonitorDto();
- monitorDto.setMonitor(monitor);
List<Param> params = paramDao.findParamsByMonitorId(id);
monitorDto.setParams(params);
if
(DispatchConstants.PROTOCOL_PROMETHEUS.equalsIgnoreCase(monitor.getApp())) {
List<CollectRep.MetricsData> metricsDataList =
warehouseService.queryMonitorMetricsData(id);
List<String> metrics =
metricsDataList.stream().map(CollectRep.MetricsData::getMetrics).collect(Collectors.toList());
monitorDto.setMetrics(metrics);
+
monitor.setGrafanaDashboard(dashboardService.getDashboardByMonitorId(id));
} else {
Job job = appService.getAppDefine(monitor.getApp());
List<String> metrics = job.getMetrics().stream()
@@ -635,6 +655,7 @@ public class MonitorServiceImpl implements MonitorService {
.map(Metrics::getName).collect(Collectors.toList());
monitorDto.setMetrics(metrics);
}
+ monitorDto.setMonitor(monitor);
Optional<CollectorMonitorBind> bindOptional =
collectorMonitorBindDao.findCollectorMonitorBindByMonitorId(monitor.getId());
bindOptional.ifPresent(bind ->
monitorDto.setCollector(bind.getCollector()));
return monitorDto;
diff --git a/manager/src/main/resources/application-test.yml
b/manager/src/main/resources/application-test.yml
index 24c06555d..d7e6cfa88 100644
--- a/manager/src/main/resources/application-test.yml
+++ b/manager/src/main/resources/application-test.yml
@@ -86,6 +86,12 @@ scheduler:
enabled: true
port: 1158
+grafana:
+ enabled: false
+ url: http://127.0.0.1:3000
+ username: admin
+ password: admin
+
# AI config
# See the documentation for details :
https://hertzbeat.apache.org/zh-cn/docs/help/aiConfig
ai:
diff --git a/manager/src/main/resources/application.yml
b/manager/src/main/resources/application.yml
index 33ad559a2..1d0d878d8 100644
--- a/manager/src/main/resources/application.yml
+++ b/manager/src/main/resources/application.yml
@@ -205,7 +205,12 @@ scheduler:
enabled: true
port: 1158
-# AI config
+grafana:
+ enabled: false
+ url: http://127.0.0.1:3000
+ username: admin
+ password: admin
+
# See the documentation for details :
https://hertzbeat.apache.org/zh-cn/docs/help/aiConfig
ai:
# AI Type:zhiPu、alibabaAi、kimiAi、sparkDesk
diff --git a/manager/src/main/resources/sureness.yml
b/manager/src/main/resources/sureness.yml
index d96f2a331..9ebd316af 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/grafana/**===get===[admin,user,guest]
+ - /api/grafana/**===post===[admin,user]
+ - /api/grafana/**===put===[admin,user]
+ - /api/grafana/**===delete===[admin]
- /api/bulletin/**===get===[admin,user,guest]
- /api/bulletin/**===post===[admin,user]
- /api/bulletin/**===put===[admin,user]
diff --git a/pom.xml b/pom.xml
index 1a3541176..ffd243e9d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -87,6 +87,7 @@
<module>remoting</module>
<module>push</module>
<module>plugin</module>
+ <module>grafana</module>
</modules>
<properties>
@@ -214,6 +215,12 @@
<artifactId>hertzbeat-plugin</artifactId>
<version>${hertzbeat.version}</version>
</dependency>
+ <!-- grafana -->
+ <dependency>
+ <groupId>org.apache.hertzbeat</groupId>
+ <artifactId>hertzbeat-grafana</artifactId>
+ <version>${hertzbeat.version}</version>
+ </dependency>
<!-- spring -->
<dependency>
<groupId>org.springframework.boot</groupId>
diff --git a/web-app/src/app/pojo/GrafanaDashboard.ts
b/web-app/src/app/pojo/GrafanaDashboard.ts
new file mode 100644
index 000000000..c84592da2
--- /dev/null
+++ b/web-app/src/app/pojo/GrafanaDashboard.ts
@@ -0,0 +1,26 @@
+/*
+ * 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.
+ */
+
+// Grafana pojo
+export class GrafanaDashboard {
+ // is enabled
+ enabled: boolean = false;
+ // grafana template json
+ template!: string;
+ // dashboard url
+ url!: string;
+}
diff --git a/web-app/src/app/pojo/Monitor.ts b/web-app/src/app/pojo/Monitor.ts
index c6e05a51e..9a633515b 100644
--- a/web-app/src/app/pojo/Monitor.ts
+++ b/web-app/src/app/pojo/Monitor.ts
@@ -17,6 +17,7 @@
* under the License.
*/
+import { GrafanaDashboard } from './GrafanaDashboard';
import { Tag } from './Tag';
export class Monitor {
@@ -33,4 +34,5 @@ export class Monitor {
gmtCreate!: number;
gmtUpdate!: number;
tags!: Tag[];
+ grafanaDashboard!: GrafanaDashboard;
}
diff --git a/web-app/src/app/routes/SafePipe.ts
b/web-app/src/app/routes/SafePipe.ts
new file mode 100644
index 000000000..021fc0dca
--- /dev/null
+++ b/web-app/src/app/routes/SafePipe.ts
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Pipe, PipeTransform } from '@angular/core';
+import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
+
+@Pipe({
+ standalone: true,
+ name: 'safe'
+})
+export class SafePipe implements PipeTransform {
+ constructor(private sanitizer: DomSanitizer) {}
+
+ transform(url: string, type: string): SafeResourceUrl {
+ return this.sanitizer.bypassSecurityTrustResourceUrl(url);
+ }
+}
diff --git
a/web-app/src/app/routes/monitor/monitor-detail/monitor-detail.component.html
b/web-app/src/app/routes/monitor/monitor-detail/monitor-detail.component.html
index 7cf402565..2c3b9d597 100755
---
a/web-app/src/app/routes/monitor/monitor-detail/monitor-detail.component.html
+++
b/web-app/src/app/routes/monitor/monitor-detail/monitor-detail.component.html
@@ -91,6 +91,15 @@
></app-monitor-data-chart>
</div>
</nz-tab>
+ <nz-tab *ngIf="monitor.grafanaDashboard.enabled"
[nzTitle]="title3Template">
+ <ng-template #title3Template>
+ <i nz-icon nzType="line-chart" style="margin-left: 10px"></i>
+ Grafana
+ </ng-template>
+ <div style="width: 100%; height: 1200px; margin-bottom: 6px">
+ <iframe [src]="monitor.grafanaDashboard.url | safe :
'resourceUrl'" width="100%" height="100%" style="border: none"> </iframe>
+ </div>
+ </nz-tab>
</nz-tabset>
</nz-spin>
</nz-content>
diff --git
a/web-app/src/app/routes/monitor/monitor-detail/monitor-detail.component.ts
b/web-app/src/app/routes/monitor/monitor-detail/monitor-detail.component.ts
index 103b72b79..76a5cf089 100644
--- a/web-app/src/app/routes/monitor/monitor-detail/monitor-detail.component.ts
+++ b/web-app/src/app/routes/monitor/monitor-detail/monitor-detail.component.ts
@@ -63,6 +63,7 @@ export class MonitorDetailComponent implements OnInit,
OnDestroy {
ngOnInit(): void {
this.countDownTime = this.deadline;
this.loadRealTimeMetric();
+ this.getGrafana();
}
loadMetricChart() {
@@ -199,6 +200,21 @@ export class MonitorDetailComponent implements OnInit,
OnDestroy {
this.cdr.detectChanges();
}
+ getGrafana() {
+ this.monitorSvc.getGrafanaDashboard(this.monitorId).subscribe(
+ message => {
+ if (message.code === 0 && message.msg != null) {
+ this.monitor.grafanaDashboard = message.data;
+ } else {
+ console.warn(message.msg);
+ }
+ },
+ error => {
+ console.error(error.msg);
+ }
+ );
+ }
+
ngOnDestroy(): void {
clearInterval(this.interval$);
}
diff --git
a/web-app/src/app/routes/monitor/monitor-edit/monitor-edit.component.ts
b/web-app/src/app/routes/monitor/monitor-edit/monitor-edit.component.ts
index 867339855..30743394d 100644
--- a/web-app/src/app/routes/monitor/monitor-edit/monitor-edit.component.ts
+++ b/web-app/src/app/routes/monitor/monitor-edit/monitor-edit.component.ts
@@ -23,10 +23,12 @@ import { ActivatedRoute, ParamMap, Router } from
'@angular/router';
import { I18NService } from '@core';
import { ALAIN_I18N_TOKEN, TitleService } from '@delon/theme';
import { NzNotificationService } from 'ng-zorro-antd/notification';
+import { NzUploadFile } from 'ng-zorro-antd/upload';
import { throwError } from 'rxjs';
import { switchMap } from 'rxjs/operators';
import { Collector } from '../../../pojo/Collector';
+import { GrafanaDashboard } from '../../../pojo/GrafanaDashboard';
import { Message } from '../../../pojo/Message';
import { Monitor } from '../../../pojo/Monitor';
import { Param } from '../../../pojo/Param';
@@ -59,6 +61,7 @@ export class MonitorEditComponent implements OnInit {
advancedParams!: Param[];
paramValueMap!: Map<String, Param>;
monitor = new Monitor();
+ grafanaDashboard!: GrafanaDashboard;
collectors!: Collector[];
collector: string = '';
detected: boolean = false;
@@ -246,4 +249,18 @@ export class MonitorEditComponent implements OnInit {
app = app ? app : '';
this.router.navigateByUrl(`/monitors?app=${app}`);
}
+
+ //start grafana
+ handleTemplateInput(event: any): any {
+ if (event.file && event.file.originFileObj) {
+ const fileReader = new FileReader();
+ fileReader.readAsText(event.file.originFileObj, 'UTF-8');
+ fileReader.onload = () => {
+ this.grafanaDashboard.template = fileReader.result as string;
+ };
+ fileReader.onerror = error => {
+ console.log(error);
+ };
+ }
+ }
}
diff --git
a/web-app/src/app/routes/monitor/monitor-form/monitor-form.component.html
b/web-app/src/app/routes/monitor/monitor-form/monitor-form.component.html
index c96cf1197..e91c22d5b 100644
--- a/web-app/src/app/routes/monitor/monitor-form/monitor-form.component.html
+++ b/web-app/src/app/routes/monitor/monitor-form/monitor-form.component.html
@@ -158,6 +158,39 @@
</nz-form-control>
</nz-form-item>
+ <nz-divider *ngIf="monitor.app === 'prometheus'"></nz-divider>
+ <!-- grafana is enabled -->
+ <nz-form-item *ngIf="monitor.app === 'prometheus'">
+ <nz-form-label nzSpan="7" nzFor="grafana"
[nzTooltipTitle]="'monitor.grafana.enabled.tip' | i18n">
+ {{ 'monitor.grafana.enabled.label' | i18n }}
+ </nz-form-label>
+ <nz-form-control nzSpan="8">
+ <nz-switch [(ngModel)]="monitor.grafanaDashboard.enabled"
name="grafana" id="grafana"></nz-switch>
+ </nz-form-control>
+ </nz-form-item>
+ <!-- upload grafana dashboard json -->
+ <nz-form-item *ngIf="monitor.grafanaDashboard.enabled && monitor.app ===
'prometheus'">
+ <nz-form-label nzSpan="7" nzFor="grafanaJson"
[nzTooltipTitle]="'monitor.grafana.upload.tip' | i18n">
+ {{ 'monitor.grafana.upload.label' | i18n }}
+ </nz-form-label>
+ <nz-form-control nzSpan="8">
+ <nz-upload
+ nzName="file"
+ nzListType="text"
+ [nzShowUploadList]="true"
+ [nzMultiple]="false"
+ [nzLimit]="1"
+ [nzCustomRequest]="handleTemplateInput"
+ (nzChange)="handleTemplateInput($event)"
+ >
+ <button nz-button>
+ <i nz-icon nzType="upload"></i>
+ {{ 'monitor.grafana.upload.label' | i18n }}
+ </button>
+ </nz-upload>
+ </nz-form-control>
+ </nz-form-item>
+
<div nz-row>
<div nz-col [nzXs]="{ span: 24 }" [nzLg]="{ span: 8, offset: 7 }"
style="text-align: center">
<button
diff --git
a/web-app/src/app/routes/monitor/monitor-form/monitor-form.component.ts
b/web-app/src/app/routes/monitor/monitor-form/monitor-form.component.ts
index 7c90e02df..6a931ec99 100644
--- a/web-app/src/app/routes/monitor/monitor-form/monitor-form.component.ts
+++ b/web-app/src/app/routes/monitor/monitor-form/monitor-form.component.ts
@@ -177,4 +177,18 @@ export class MonitorFormComponent implements OnChanges {
}
});
}
+
+ //start grafana
+ handleTemplateInput(event: any): any {
+ if (event.file && event.file.originFileObj) {
+ const fileReader = new FileReader();
+ fileReader.readAsText(event.file.originFileObj, 'UTF-8');
+ fileReader.onload = () => {
+ this.monitor.grafanaDashboard.template = fileReader.result as string;
+ };
+ fileReader.onerror = error => {
+ console.log(error);
+ };
+ }
+ }
}
diff --git
a/web-app/src/app/routes/monitor/monitor-list/monitor-list.component.ts
b/web-app/src/app/routes/monitor/monitor-list/monitor-list.component.ts
index a686d786e..1184a6bbd 100644
--- a/web-app/src/app/routes/monitor/monitor-list/monitor-list.component.ts
+++ b/web-app/src/app/routes/monitor/monitor-list/monitor-list.component.ts
@@ -312,6 +312,10 @@ export class MonitorListComponent implements OnInit,
OnDestroy {
this.notifySvc.error(this.i18nSvc.fanyi('common.notify.delete-fail'),
error.msg);
}
);
+ // delete grafana dashboard
+ for (let monitorId of monitors) {
+ this.deleteGrafanaDashboard(monitorId);
+ }
}
updatePageIndex(delSize: number) {
@@ -575,5 +579,20 @@ export class MonitorListComponent implements OnInit,
OnDestroy {
// end: app type search filter
+ deleteGrafanaDashboard(monitorId: number) {
+ this.monitorSvc.deleteGrafanaDashboard(monitorId).subscribe(
+ message => {
+ if (message.code === 0) {
+ console.log('delete grafana dashboard success');
+ } else {
+ console.warn(message.msg);
+ }
+ },
+ error => {
+ console.error(error.msg);
+ }
+ );
+ }
+
protected readonly sliceTagName = formatTagName;
}
diff --git
a/web-app/src/app/routes/monitor/monitor-new/monitor-new.component.ts
b/web-app/src/app/routes/monitor/monitor-new/monitor-new.component.ts
index 7dc647297..0a5e6c9d6 100644
--- a/web-app/src/app/routes/monitor/monitor-new/monitor-new.component.ts
+++ b/web-app/src/app/routes/monitor/monitor-new/monitor-new.component.ts
@@ -25,6 +25,7 @@ import { NzNotificationService } from
'ng-zorro-antd/notification';
import { switchMap } from 'rxjs/operators';
import { Collector } from '../../../pojo/Collector';
+import { GrafanaDashboard } from '../../../pojo/GrafanaDashboard';
import { Message } from '../../../pojo/Message';
import { Monitor } from '../../../pojo/Monitor';
import { Param } from '../../../pojo/Param';
@@ -64,6 +65,7 @@ export class MonitorNewComponent implements OnInit {
) {
this.monitor = new Monitor();
this.monitor.tags = [];
+ this.monitor.grafanaDashboard = new GrafanaDashboard();
}
ngOnInit(): void {
diff --git a/web-app/src/app/routes/monitor/monitor.module.ts
b/web-app/src/app/routes/monitor/monitor.module.ts
index abef9b4cf..10e6f5bd2 100644
--- a/web-app/src/app/routes/monitor/monitor.module.ts
+++ b/web-app/src/app/routes/monitor/monitor.module.ts
@@ -32,6 +32,7 @@ import { NzTagModule } from 'ng-zorro-antd/tag';
import { NzUploadModule } from 'ng-zorro-antd/upload';
import { NgxEchartsModule } from 'ngx-echarts';
+import { SafePipe } from '../SafePipe';
import { MonitorDataChartComponent } from
'./monitor-data-chart/monitor-data-chart.component';
import { MonitorDataTableComponent } from
'./monitor-data-table/monitor-data-table.component';
import { MonitorDetailComponent } from
'./monitor-detail/monitor-detail.component';
@@ -66,6 +67,7 @@ const COMPONENTS: Array<Type<void>> = [
NzCollapseModule,
ClipboardModule,
NzUploadModule,
+ SafePipe,
NzListModule
],
declarations: COMPONENTS
diff --git a/web-app/src/app/service/monitor.service.ts
b/web-app/src/app/service/monitor.service.ts
index ba215cb51..c2771e8bb 100644
--- a/web-app/src/app/service/monitor.service.ts
+++ b/web-app/src/app/service/monitor.service.ts
@@ -32,6 +32,7 @@ const manage_monitors_uri = '/monitors/manage';
const export_monitors_uri = '/monitors/export';
const summary_uri = '/summary';
const warehouse_storage_status_uri = '/warehouse/storage/status';
+const grafana_dashboard_uri = '/grafana/dashboard';
@Injectable({
providedIn: 'root'
@@ -184,4 +185,12 @@ export class MonitorService {
public getWarehouseStorageServerStatus(): Observable<Message<any>> {
return this.http.get<Message<any>>(warehouse_storage_status_uri);
}
+
+ public getGrafanaDashboard(monitorId: number): Observable<Message<any>> {
+ return
this.http.get<Message<any>>(`${grafana_dashboard_uri}?monitorId=${monitorId}`);
+ }
+
+ public deleteGrafanaDashboard(monitorId: number): Observable<Message<any>> {
+ return
this.http.delete<Message<any>>(`${grafana_dashboard_uri}?monitorId=${monitorId}`);
+ }
}
diff --git a/web-app/src/assets/i18n/en-US.json
b/web-app/src/assets/i18n/en-US.json
index f7fc8ae19..15f972f0e 100644
--- a/web-app/src/assets/i18n/en-US.json
+++ b/web-app/src/assets/i18n/en-US.json
@@ -126,7 +126,13 @@
"down": "Down",
"unreachable": "Unreachable",
"paused": "Paused"
- }
+ },
+ "grafana": {
+ "enabled.tip": "is enabled, the monitoring data will be displayed in
Grafana",
+ "enabled.label": "Enable Grafana",
+ "upload.tip": "Upload Grafana template file, support .json file",
+ "upload.label": "Upload Grafana Template"
+ }
},
"alert": {
"": "Alert",
diff --git a/web-app/src/assets/i18n/zh-CN.json
b/web-app/src/assets/i18n/zh-CN.json
index 45e7174ab..99482d15e 100644
--- a/web-app/src/assets/i18n/zh-CN.json
+++ b/web-app/src/assets/i18n/zh-CN.json
@@ -112,6 +112,12 @@
"down": "宕机",
"unreachable": "不可达",
"paused": "暂停"
+ },
+ "grafana": {
+ "enabled.tip": "是否启用Grafana",
+ "enabled.label": "启用Grafana",
+ "upload.tip": "上传Grafana的JSON文件",
+ "upload.label": "上传Grafana模板"
}
},
"alert": {
diff --git a/web-app/src/assets/i18n/zh-TW.json
b/web-app/src/assets/i18n/zh-TW.json
index f52a0a9d3..d873c2fe8 100644
--- a/web-app/src/assets/i18n/zh-TW.json
+++ b/web-app/src/assets/i18n/zh-TW.json
@@ -126,7 +126,13 @@
"down": "宕機",
"unreachable": "不可達",
"paused": "暫停"
- }
+ },
+ "grafana": {
+ "enabled.tip": "是否啓用Grafana監控",
+ "enabled.label": "啓用Grafana監控",
+ "upload.tip": "上傳Grafana模板文件",
+ "upload.label": "上傳Grafana模板"
+ }
},
"alert": {
"": "告警",
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]