|
package mytest;
|
|
|
|
import java.io.BufferedReader;
|
|
import java.io.BufferedWriter;
|
|
import java.io.File;
|
|
import java.io.FileInputStream;
|
|
import java.io.FileWriter;
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.io.InputStreamReader;
|
|
import java.sql.Connection;
|
|
import java.sql.DriverManager;
|
|
import java.sql.PreparedStatement;
|
|
import java.sql.SQLException;
|
|
import java.sql.Statement;
|
|
import java.util.Collections;
|
|
import java.util.HashMap;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
|
|
import org.json.JSONArray;
|
|
import org.json.JSONObject;
|
|
import org.springframework.http.HttpEntity;
|
|
import org.springframework.http.HttpHeaders;
|
|
import org.springframework.http.HttpMethod;
|
|
import org.springframework.http.HttpStatus;
|
|
import org.springframework.http.MediaType;
|
|
import org.springframework.http.ResponseEntity;
|
|
import org.springframework.web.client.RestTemplate;
|
|
|
|
import com.fasterxml.jackson.core.exc.StreamWriteException;
|
|
import com.fasterxml.jackson.databind.DatabindException;
|
|
import com.fasterxml.jackson.databind.JsonNode;
|
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
|
|
|
public class GrafanaETL {
|
|
|
|
private static ObjectMapper objectMapper = new ObjectMapper();
|
|
private static Connection conn;
|
|
|
|
private static final String DB_URL = "jdbc:mysql://192.168.10.209:3306/dev_grafana";
|
|
private static final String DB_USERNAME = "grafana";
|
|
private static final String DB_PASSWORD = "grafana16313302";
|
|
|
|
private static final String GRAFANA_A_URL = "https://gfn.veri-id-prd.com";
|
|
private static final String GRAFANA_B_URL = "http://192.168.10.209:3001";
|
|
private static final String API_KEY_A = "Bearer glsa_JBbC5bQZ0Jpj1N0B5cW89oIf6zckwH52_8c600c43"; // Grafana A 的 API
|
|
// Key
|
|
private static final String API_KEY_B = "Basic YWRtaW46YWRtaW4xNjMxMzMwMg=="; // Grafana B 的 API Key
|
|
private static final RestTemplate restTemplate = new RestTemplate();
|
|
|
|
private static final String BACKUP_FOLDER = "D:\\TEMP\\PRD\\";
|
|
|
|
public static void main(String args[]) {
|
|
|
|
try {
|
|
conn = getConnection();
|
|
// backupAllFolders();
|
|
// backupAllDashboardAndLibs();
|
|
// backupDataSource();
|
|
|
|
// importAllFolders();
|
|
// importAllDataSource();
|
|
|
|
// importAllLibrary();
|
|
importAllDashboard();
|
|
} catch (Exception e) {
|
|
e.printStackTrace();
|
|
} finally {
|
|
if (conn != null)
|
|
try {
|
|
conn.close();
|
|
} catch (SQLException e) {
|
|
// TODO Auto-generated catch block
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
public static void backupAllFolders() throws Exception {
|
|
|
|
List<Map<String, Object>> folders = getAllFoldersFromGrafanaA();
|
|
ObjectMapper objectMapper = new ObjectMapper();
|
|
String json = objectMapper.writeValueAsString(folders);
|
|
writeToFile(BACKUP_FOLDER + "//folders.json", json);
|
|
|
|
}
|
|
|
|
public static void importAllFolders() {
|
|
|
|
try {
|
|
|
|
String deleteSQL = "delete from dashboard le where is_folder = 1";
|
|
Statement stat = conn.createStatement();
|
|
stat.execute(deleteSQL);
|
|
stat.close();
|
|
// 1️⃣ 取得 Grafana A 的所有資料夾
|
|
List<Map<String, Object>> folders = getAllFoldersFromGrafanaA();
|
|
|
|
// 2️⃣ 匯入資料夾到 Grafana B
|
|
for (Map<String, Object> folder : folders) {
|
|
createFolderInGrafanaB(folder);
|
|
}
|
|
|
|
System.out.println("✅ 資料夾同步完成!");
|
|
|
|
} catch (Exception e) {
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
|
|
public static List getAllFoldersFromGrafanaA() throws Exception {
|
|
|
|
String url = GRAFANA_A_URL + "/api/folders";
|
|
HttpHeaders headers = new HttpHeaders();
|
|
headers.set("Authorization", API_KEY_A);
|
|
headers.setContentType(MediaType.APPLICATION_JSON);
|
|
HttpEntity<String> entity = new HttpEntity<>(headers);
|
|
|
|
ResponseEntity<List> response = restTemplate.exchange(url, HttpMethod.GET, entity, List.class);
|
|
if (response.getStatusCode() == HttpStatus.OK) {
|
|
return (response.getBody());
|
|
// writeToFile(BACKUP_FOLDER + "//folders.json", new
|
|
// JSONArray(response.getBody()).toString());
|
|
}
|
|
return Collections.emptyList();
|
|
}
|
|
|
|
public static void createFolderInGrafanaB(Map<String, Object> folder) throws Exception {
|
|
|
|
String url = GRAFANA_B_URL + "/api/folders";
|
|
HttpHeaders headers = new HttpHeaders();
|
|
headers.set("Authorization", API_KEY_B);
|
|
headers.setContentType(MediaType.APPLICATION_JSON);
|
|
|
|
// 準備要傳遞的 JSON 資料
|
|
Map<String, Object> requestBody = new HashMap<>();
|
|
requestBody.put("title", folder.get("title")); // 設定資料夾名稱
|
|
requestBody.put("uid", folder.get("uid")); // 保留原始 UID
|
|
|
|
HttpEntity<Map<String, Object>> entity = new HttpEntity<>(requestBody, headers);
|
|
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, entity, String.class);
|
|
|
|
if (response.getStatusCode() == HttpStatus.OK || response.getStatusCode() == HttpStatus.CONFLICT) {
|
|
System.out.println("✅ 匯入資料夾: " + folder.get("title"));
|
|
String deletePermission = "delete from permission p where "
|
|
+ "`scope` like '%"+folder.get("uid")+"%' "
|
|
+ " and (role_id = 2 or role_id = 3)";
|
|
Statement stat = conn.createStatement();
|
|
stat.execute(deletePermission);
|
|
stat.close();
|
|
} else {
|
|
System.out.println("❌ 無法匯入資料夾: " + folder.get("title") + ",錯誤:" + response.getBody());
|
|
}
|
|
|
|
}
|
|
|
|
public static void backupAllDashboardAndLibs() {
|
|
try {
|
|
|
|
RestTemplate restTemplate = new RestTemplate();
|
|
HttpHeaders headers = new HttpHeaders();
|
|
headers.set("Authorization", API_KEY_A);
|
|
headers.setContentType(MediaType.APPLICATION_JSON);
|
|
|
|
String searchUrl = GRAFANA_A_URL + "/api/search?type=dash-db";
|
|
HttpEntity<String> request = new HttpEntity<>(headers);
|
|
ResponseEntity<String> searchResponse = restTemplate.exchange(searchUrl, HttpMethod.GET, request,
|
|
String.class);
|
|
|
|
ObjectMapper objectMapper = new ObjectMapper();
|
|
JsonNode searchResult = objectMapper.readTree(searchResponse.getBody());
|
|
|
|
for (JsonNode dashboard : searchResult) {
|
|
|
|
String dashboardUid = dashboard.get("uid").asText();
|
|
|
|
String dashboardTitle = dashboard.get("title").asText();
|
|
|
|
String dashboardUrl = GRAFANA_A_URL + "/api/dashboards/uid/" + dashboardUid;
|
|
ResponseEntity<String> dashboardResponse = restTemplate.exchange(dashboardUrl, HttpMethod.GET, request,
|
|
String.class);
|
|
JsonNode dashboardJson = objectMapper.readTree(dashboardResponse.getBody());
|
|
|
|
writeToFile(BACKUP_FOLDER + "dashboard\\" + dashboardTitle + ".json", dashboardJson);
|
|
|
|
JsonNode panels = dashboardJson.path("dashboard").path("panels");
|
|
for (JsonNode panel : panels) {
|
|
|
|
if (panel.has("libraryPanel")) {
|
|
|
|
String libraryPanelUid = panel.get("libraryPanel").get("uid").asText();
|
|
System.out.println("Found library panel with UID: " + libraryPanelUid);
|
|
|
|
String libraryElementsUrl = GRAFANA_A_URL + "/api/library-elements/" + libraryPanelUid;
|
|
|
|
HttpEntity<String> libraryRequest = new HttpEntity<>(headers);
|
|
ResponseEntity<String> libraryResponse = restTemplate.exchange(libraryElementsUrl,
|
|
HttpMethod.GET, libraryRequest, String.class);
|
|
|
|
String resBody = libraryResponse.getBody();
|
|
|
|
JsonNode libraryElementsJson = objectMapper.readTree(resBody);
|
|
|
|
writeToFile(BACKUP_FOLDER + "library\\"
|
|
+ libraryElementsJson.path("result").get("name").asText() + ".json",
|
|
libraryElementsJson);
|
|
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
} catch (Exception e) {
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
|
|
private static void backupDataSource() throws Exception {
|
|
|
|
String urlA = GRAFANA_A_URL + "/api/datasources";
|
|
HttpHeaders headers = new HttpHeaders();
|
|
headers.set("Authorization", API_KEY_A);
|
|
headers.setContentType(MediaType.APPLICATION_JSON);
|
|
HttpEntity<String> entity = new HttpEntity<>(headers);
|
|
|
|
ResponseEntity<String> response = restTemplate.exchange(urlA, HttpMethod.GET, entity, String.class);
|
|
if (response.getStatusCode() == HttpStatus.OK) {
|
|
System.out.println(response.getBody());
|
|
writeToFile(BACKUP_FOLDER + "//datasource.json", new JSONArray(response.getBody()).toString());
|
|
}
|
|
|
|
}
|
|
|
|
public static void importAllDataSource() {
|
|
|
|
try {
|
|
|
|
String deleteSQL = "delete from data_source";
|
|
Statement stat = conn.createStatement();
|
|
stat.execute(deleteSQL);
|
|
stat.close();
|
|
|
|
String datasourceList = getAllDataSourceFromGrafanaA();
|
|
|
|
JsonNode dataSources = objectMapper.readTree(datasourceList);
|
|
|
|
for (JsonNode datasource : dataSources) {
|
|
createDataSourceInGrafanaB(datasource);
|
|
}
|
|
|
|
System.out.println("✅ DataSource同步完成!");
|
|
|
|
} catch (Exception e) {
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
|
|
public static String getAllDataSourceFromGrafanaA() throws Exception {
|
|
|
|
String url = GRAFANA_A_URL + "/api/datasources";
|
|
HttpHeaders headers = new HttpHeaders();
|
|
headers.set("Authorization", API_KEY_A);
|
|
headers.setContentType(MediaType.APPLICATION_JSON);
|
|
HttpEntity<String> entity = new HttpEntity<>(headers);
|
|
|
|
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, entity, String.class);
|
|
if (response.getStatusCode() == HttpStatus.OK) {
|
|
return (response.getBody());
|
|
}
|
|
return "";
|
|
}
|
|
|
|
public static void createDataSourceInGrafanaB(JsonNode datasource) throws Exception {
|
|
|
|
String url = GRAFANA_B_URL + "/api/datasources";
|
|
HttpHeaders headers = new HttpHeaders();
|
|
headers.set("Authorization", API_KEY_B);
|
|
headers.setContentType(MediaType.APPLICATION_JSON);
|
|
|
|
((ObjectNode) datasource).remove("id");
|
|
String dataSourceJson = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(datasource);
|
|
|
|
HttpEntity<String> entity = new HttpEntity<>(dataSourceJson, headers);
|
|
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, entity, String.class);
|
|
|
|
if (response.getStatusCode() == HttpStatus.OK || response.getStatusCode() == HttpStatus.CONFLICT) {
|
|
System.out.println("✅ 匯入DataSource: " + datasource.get("name").asText());
|
|
} else {
|
|
System.out.println("❌ 無法匯入DataSource: " + datasource.get("name").asText() + ",錯誤:" + response.getBody());
|
|
}
|
|
|
|
}
|
|
|
|
public static void importAllLibrary() throws Exception {
|
|
|
|
String folderPath = BACKUP_FOLDER + "library\\";
|
|
System.out.println(folderPath);
|
|
File dir = new File(folderPath);
|
|
if (dir.isDirectory()) {
|
|
File[] fs = dir.listFiles();
|
|
for (int i = 0; i < fs.length; i++) {
|
|
importLibrary(fs[i]);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
public static void importLibrary(File f) throws Exception {
|
|
|
|
String titleName = "";
|
|
try {
|
|
|
|
String jsonStr = readFile(new FileInputStream(f), "UTF-8");
|
|
JSONObject jsonObject = new JSONObject(jsonStr).getJSONObject("result");
|
|
|
|
titleName = jsonObject.getString("name");
|
|
|
|
ObjectMapper objectMapper = new ObjectMapper();
|
|
Map<String, Object> jsonMap = jsonObject.getJSONObject("model").toMap();
|
|
String jsonModel = objectMapper.writeValueAsString(jsonMap);
|
|
|
|
String deleteSQL = "DELETE FROM library_element WHERE uid = ?";
|
|
PreparedStatement pstmt = conn.prepareStatement(deleteSQL);
|
|
pstmt.setString(1, jsonObject.getString("uid"));
|
|
pstmt.executeUpdate();
|
|
pstmt.close();
|
|
|
|
String insertSQL = "INSERT INTO library_element "
|
|
+ "(org_id, folder_id, uid, name, kind, type, description, model, created, created_by, updated, updated_by, version) "
|
|
+ "VALUES (?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, ?, CURRENT_TIMESTAMP, ?, ?)";
|
|
|
|
pstmt = conn.prepareStatement(insertSQL);
|
|
pstmt.setInt(1, jsonObject.getInt("orgId"));
|
|
pstmt.setInt(2, 0);
|
|
pstmt.setString(3, jsonObject.getString("uid"));
|
|
pstmt.setString(4, jsonObject.getString("name"));
|
|
pstmt.setInt(5, jsonObject.getInt("kind"));
|
|
pstmt.setString(6, jsonObject.getString("type"));
|
|
pstmt.setString(7, ""); // description 預設為空
|
|
pstmt.setString(8, jsonModel); // **正確存入 `MEDIUMTEXT` 欄位**
|
|
pstmt.setInt(9, 1); // created_by 預設為 1
|
|
pstmt.setInt(10, 1); // updated_by 預設為 1
|
|
pstmt.setInt(11, 1); // version 預設為 1
|
|
|
|
pstmt.executeUpdate();
|
|
pstmt.close();
|
|
|
|
String updateSQL = "UPDATE library_element SET folder_id = (SELECT id FROM dashboard WHERE uid = ?) WHERE uid = ?";
|
|
pstmt = conn.prepareStatement(updateSQL);
|
|
pstmt.setString(1, jsonObject.getString("folderUid"));
|
|
pstmt.setString(2, jsonObject.getString("uid"));
|
|
pstmt.executeUpdate();
|
|
pstmt.close();
|
|
|
|
System.out.println("✅ 匯入 Library: " + titleName);
|
|
|
|
} catch (Exception e) {
|
|
e.printStackTrace();
|
|
System.out.println("insert " + titleName + " failed.");
|
|
}
|
|
}
|
|
|
|
public static void importAllDashboard() throws Exception {
|
|
|
|
String deleteSQL = "delete from dashboard le where is_folder = 0";
|
|
Statement stat = conn.createStatement();
|
|
stat.execute(deleteSQL);
|
|
stat.close();
|
|
|
|
String folderPath = BACKUP_FOLDER + "dashboard\\";
|
|
System.out.println(folderPath);
|
|
File dir = new File(folderPath);
|
|
if (dir.isDirectory()) {
|
|
File[] fs = dir.listFiles();
|
|
for (int i = 0; i < fs.length; i++) {
|
|
importDashboard(fs[i]);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
private static void importDashboard(File f) {
|
|
|
|
String title = "";
|
|
|
|
try {
|
|
|
|
String dashboardString = readFile(new FileInputStream(f), "UTF-8");
|
|
JSONObject metaObj = new JSONObject(dashboardString).getJSONObject("meta");
|
|
JSONObject dashboardObj = new JSONObject(dashboardString).getJSONObject("dashboard");
|
|
|
|
dashboardObj.remove("id");
|
|
title = dashboardObj.getString("title");
|
|
|
|
JSONObject importObj = new JSONObject();
|
|
|
|
importObj.put("dashboard", dashboardObj);
|
|
importObj.put("folderUid", metaObj.getString("folderUid"));
|
|
importObj.put("overwrite", true);
|
|
importObj.put("inputs", new JSONArray());
|
|
importObj.put("version", 1);
|
|
|
|
String urlB = GRAFANA_B_URL + "/api/dashboards/import";
|
|
HttpHeaders headersB = new HttpHeaders();
|
|
headersB.set("Authorization", API_KEY_B);
|
|
headersB.setContentType(MediaType.APPLICATION_JSON);
|
|
|
|
String importString = importObj.toString();
|
|
//String importString = importObj.toString().replaceAll("\"collapsed\" : false", "\"collapsed\" : true");
|
|
|
|
HttpEntity<String> entityB = new HttpEntity<>(importString, headersB);
|
|
|
|
ResponseEntity<String> responseB = restTemplate.exchange(urlB, HttpMethod.POST, entityB, String.class);
|
|
|
|
if (responseB.getStatusCode() == HttpStatus.OK || responseB.getStatusCode() == HttpStatus.CREATED) {
|
|
System.out.println("✅ 匯入 Dashboard: " + dashboardObj.get("title"));
|
|
|
|
String deletePermission = "delete from permission p where "
|
|
+ "`scope` like '%"+dashboardObj.getString("uid")+"%' "
|
|
+ " and (role_id = 2 or role_id = 3)";
|
|
Statement stat = conn.createStatement();
|
|
stat.execute(deletePermission);
|
|
stat.close();
|
|
|
|
} else {
|
|
System.out.println(
|
|
"❌ 無法匯入 Dashboard: " + dashboardObj.get("title") + ",錯誤:" + responseB.getBody());
|
|
}
|
|
|
|
} catch (Exception e) {
|
|
System.out.println(
|
|
"❌ 無法匯入 Dashboard: " + title + ",錯誤:");
|
|
e.printStackTrace();
|
|
}
|
|
|
|
}
|
|
|
|
private static void writeToFile(String fileName, String data)
|
|
throws StreamWriteException, DatabindException, IOException {
|
|
|
|
ObjectMapper objectMapper = new ObjectMapper();
|
|
File outputFile = new File(fileName);
|
|
if (!outputFile.getParentFile().exists()) {
|
|
outputFile.getParentFile().mkdirs();
|
|
}
|
|
BufferedWriter writer = new BufferedWriter(new FileWriter(fileName));
|
|
writer.write(data);
|
|
writer.flush();
|
|
writer.close();
|
|
|
|
}
|
|
|
|
private static void writeToFile(String fileName, JsonNode data)
|
|
throws StreamWriteException, DatabindException, IOException {
|
|
|
|
ObjectMapper objectMapper = new ObjectMapper();
|
|
File outputFile = new File(fileName);
|
|
if (!outputFile.getParentFile().exists()) {
|
|
outputFile.getParentFile().mkdirs();
|
|
}
|
|
objectMapper.writerWithDefaultPrettyPrinter().writeValue(outputFile, data);
|
|
System.out.println("Dashboard saved to file: " + outputFile.getName());
|
|
|
|
}
|
|
|
|
public static String readFile(InputStream fis, String encoding) {
|
|
|
|
StringBuffer sb = new StringBuffer();
|
|
|
|
try {
|
|
|
|
BufferedReader in = new BufferedReader(new InputStreamReader(fis, encoding));
|
|
try {
|
|
String data = null;
|
|
while ((data = in.readLine()) != null) {
|
|
sb.append(data);
|
|
}
|
|
} finally {
|
|
in.close();
|
|
fis.close();
|
|
}
|
|
} catch (Exception e) {
|
|
e.printStackTrace();
|
|
}
|
|
return sb.toString();
|
|
}
|
|
|
|
private static Connection getConnection() throws Exception {
|
|
|
|
Class.forName("com.mysql.cj.jdbc.Driver");
|
|
return DriverManager.getConnection(DB_URL, DB_USERNAME, DB_PASSWORD);
|
|
|
|
}
|
|
}
|