【Grafana】grafana-image-renderer配合python腳本實現儀表盤導出pdf

背景

  • os:centos7
  • Grafana:v12
  • grafana-image-renderer:v4.0.10
  • 插件:否

grafana-image-renderer可以以插件形式啟動,也可以以單獨服務啟動,在centos7插件啟動時,報錯glibc版本太低,未找到Glibc_2.27,所以以單獨服務啟動。

一、安裝Grafana

# 解壓部署
tar zxvf grafana-12.1.0.linux-amd64.tar.gz -C /usr/local/
cd /usr/local/grafana-v12.1.0/
# 編輯配置文件
vi conf/defaults.ini
# 調試簡單
[auth.anonymous]
enabled = true
# 配置rendering地址
[rendering]
server_url = http://192.168.113.138:8081/render
callback_url = http://192.168.113.138:3000/
# 啟動服務
nohup bin/grafana server &

二、啟動grafana-image-renderer

docker network create grafana
docker pull docker.1ms.run/grafana/grafana-image-renderer:v4.0.10
docker run --network grafana --name renderer -p 8081:8081 --rm --detach docker.1ms.run/grafana/grafana-image-renderer:v4.0.10

在這里插入圖片描述

三、測試接口

直接訪問:http://192.168.113.138:3000/render/d-solo/bdd48668-7073-4adc-b548-276133845e71?orgId=1&panelId=2&width=1000&height=500&from=now-24h&to=now
在這里插入圖片描述

四、編寫py腳本及效果

腳本由AI大模型生成。如果有能力可以修改Grafana源碼,頁面傳入DASHBOARD_UID 即可實現點擊下載。

import requests
import time
from typing import List, Dict
from PIL import Image, ImageDraw, ImageFont  # ? 確保包含 ImageFont
import img2pdf
from datetime import datetime
import os# ================== 配置區 ==================
GRAFANA_HOST = "http://192.168.113.138:3000"
API_KEY = "YOUR_API_KEY"  # Service Account Token 或 API Key
DASHBOARD_UID = "bdd48668-7073-4adc-b548-276133845e71"  # 替換為你的 Dashboard UID
OUTPUT_DIR = "panels_output"  # 輸出目錄
DELAY_BETWEEN_SHOTS = 1.0  # 每次截圖間隔(秒),避免渲染服務壓力過大
FROM = "now-24h"
TO = "now"
WIDTH = 1200
HEIGHT = 600
TIMEOUT = 30
# ==========================================
# 創建輸出目錄
os.makedirs(OUTPUT_DIR, exist_ok=True)
headers = {"Authorization": f"Bearer {API_KEY}","User-Agent": "Grafana-Report-Service/v1.0"
}def get_dashboard_json(uid: str) -> Dict:"""獲取 Dashboard JSON 結構"""url = f"{GRAFANA_HOST}/api/dashboards/uid/{uid}"response = requests.get(url, headers=headers, timeout=10)if response.status_code != 200:raise Exception(f"獲取 Dashboard 失敗: {response.status_code} {response.text}")return response.json()def extract_panels(dashboard_data: Dict) -> List[Dict]:"""精準提取所有可渲染的 panel(排除 row、text 等不可截圖類型)基于 Grafana v12 的 Dashboard JSON 結構"""panels = []dashboard = dashboard_data["dashboard"]skip_types = {"row", "text", "alertlist", "dashboard-link", "separator"}def is_renderable_panel(obj):# 必須是字典,有 id 和 typeif not isinstance(obj, dict):return Falseif "id" not in obj or "type" not in obj:return Falsepanel_type = obj["type"]# 排除不可渲染的類型if panel_type in skip_types:return False# 確保 id 是數字(真實的 panel id)if not isinstance(obj["id"], int):return Falsereturn Truedef walk(obj):if isinstance(obj, dict):# 如果當前對象是可渲染 panel,加入結果if is_renderable_panel(obj):title = obj.get("title") or f"Panel_{obj['id']}"panels.append({"id": obj["id"],"title": title.strip(),"type": obj["type"]})# 遞歸遍歷所有值for value in obj.values():walk(value)elif isinstance(obj, list):for item in obj:walk(item)walk(dashboard)return panelsdef sanitize_filename(name: str) -> str:"""清理文件名,移除不合法字符"""return "".join(c if c.isalnum() or c in " _-." else "_" for c in name)def render_panel_to_png(panel_id: int, title: str, output_path: str) -> bool:"""渲染單個 panel 為 PNG"""slug = DASHBOARD_UIDrender_url = f"{GRAFANA_HOST}/render/d-solo/{DASHBOARD_UID}/{slug}"params = {"orgId": 1,"panelId": panel_id,"width": WIDTH,"height": HEIGHT,"tz": "Asia/Shanghai","from": FROM,"to": TO,"deviceScaleFactor": 1.5,"renderFormat": "png"}try:response = requests.get(render_url,params=params,headers=headers,timeout=TIMEOUT)if response.status_code == 200:with open(output_path, "wb") as f:f.write(response.content)print(f"? [{panel_id}] '{title}' → 保存成功: {output_path}")return Trueelse:print(f"? [{panel_id}] '{title}' → 渲染失敗: {response.status_code}")print(f"   響應: {response.text[:200]}")return Falseexcept Exception as e:print(f"? [{panel_id}] '{title}' → 異常: {e}")return Falsedef generate_pdf_report(dashboard_title: str, panel_images: list, output_pdf: str):"""使用 img2pdf + Pillow 生成 PDF 報告panel_images: 圖片文件路徑列表(字符串)"""try:# ========== 1. 創建封面頁 ==========try:# 嘗試使用黑體(Windows 通常有 simhei.ttf)font_title = ImageFont.truetype("simhei.ttf", 60)font_text = ImageFont.truetype("simhei.ttf", 40)except:# 如果沒有中文字體,用默認(可能顯示亂碼)font_title = ImageFont.load_default()font_text = ImageFont.load_default()# 使用與面板圖像相同的寬度panel_width = WIDTH  # 從配置中獲取寬度,默認為1200# 保持寬高比計算封面高度(使用A4紙的寬高比)cover_height = int(panel_width * 1.414)  # A4紙的寬高比約為1:1.414cover = Image.new('RGB', (panel_width, cover_height), 'white')draw = ImageDraw.Draw(cover)# 繪制標題title_pos = (panel_width // 2, cover_height // 3)  # 居中 X,Y=1/3處time_pos = (panel_width // 2, cover_height // 2)  # 居中 X,Y=1/2處# 獲取文本邊界框以居中bbox_title = draw.textbbox((0, 0), dashboard_title, font=font_title)title_width = bbox_title[2] - bbox_title[0]draw.text((panel_width // 2 - title_width // 2, cover_height // 3),dashboard_title, fill="black", font=font_title)generate_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")time_text = f"生成時間:{generate_time}"bbox_time = draw.textbbox((0, 0), time_text, font=font_text)time_width = bbox_time[2] - bbox_time[0]draw.text((panel_width // 2 - time_width // 2, cover_height // 2),time_text, fill="gray", font=font_text)# 保存封面cover_path = os.path.join(OUTPUT_DIR, "cover_page.png")cover.save(cover_path, "PNG")print(f"🎨 封面頁已生成: {cover_path} (寬度: {panel_width}px)")# ========== 2. 構建圖片路徑列表 ==========# 確保所有 panel_images 都是字符串路徑,且文件存在image_paths = [cover_path]  # 先加封面for img_path in panel_images:if isinstance(img_path, str) and os.path.exists(img_path):image_paths.append(img_path)else:print(f"?? 跳過不存在的圖片: {img_path}")if len(image_paths) < 2:print("? 沒有足夠的圖片生成 PDF")return False# ========== 3. 轉換為 PDF ==========with open(output_pdf, "wb") as f:f.write(img2pdf.convert(image_paths))print(f"📄 PDF 報告已生成: {output_pdf}")return Trueexcept Exception as e:print(f"? PDF 生成失敗: {e}")import tracebacktraceback.print_exc()return Falsedef main():print(f"🔍 正在獲取 Dashboard: {DASHBOARD_UID}")try:data = get_dashboard_json(DASHBOARD_UID)dashboard_title = data["dashboard"]["title"]print(f"📊 獲取成功: '{dashboard_title}'")panels = extract_panels(data)print(f"📦 共找到 {len(panels)} 個可渲染的 panel\n")success_count = 0image_paths = []for i, panel in enumerate(panels, 1):title_clean = sanitize_filename(panel["title"])output_file = f"panel_{panel['id']}_{title_clean}.png"output_path = os.path.join(OUTPUT_DIR, output_file)print(f"🖼?  [{i}/{len(panels)}] 渲染中: {panel['title']}")if render_panel_to_png(panel["id"], panel["title"], output_path):success_count += 1image_paths.append(output_path)  # 收集成功生成的圖片time.sleep(DELAY_BETWEEN_SHOTS)print(f"\n🎉 批量截圖完成!成功: {success_count}/{len(panels)} 個")# ==== 生成 PDF ====if success_count > 0:output_pdf = f"report_{DASHBOARD_UID}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.pdf"generate_pdf_report(dashboard_title, sorted(image_paths), output_pdf)else:print("? 沒有成功截圖,跳過 PDF 生成")except Exception as e:print(f"💥 執行失敗: {e}")if __name__ == "__main__":main()

Grafana可視化:
在這里插入圖片描述

效果:
在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/93874.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/93874.shtml
英文地址,請注明出處:http://en.pswp.cn/web/93874.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

靜/動態庫 IIC(arm) day58

十七&#xff1a;動態庫和靜態庫 庫&#xff1a;一堆可執行二進制文件的集合&#xff0c;由若干個.o文件歸并生成 一&#xff1a;靜態(鏈接)庫&#xff1a;libxxx.a 生成一個獨立的可執行程序(運行時僅需要一個文件即可) 使用方便 不需要安裝 文件比較大 多個程序使用同一個靜態…

uniapp 手寫簽名組件開發全攻略

引言在移動應用開發中&#xff0c;手寫簽名功能是一個常見的需求&#xff0c;特別是在電子合同、審批流程、金融交易等場景中。本文將詳細介紹如何基于uni-app框架開發一個高性能、功能豐富的手寫簽名組件&#xff0c;并分享開發過程中的技術要點和最佳實踐。組件概述這個簽名組…

理解JavaScript中的函數賦值和調用

&#x1f468; 作者簡介&#xff1a;大家好&#xff0c;我是Taro&#xff0c;全棧領域創作者 ?? 個人主頁&#xff1a;唐璜Taro &#x1f680; 支持我&#xff1a;點贊&#x1f44d;&#x1f4dd; 評論 ??收藏 文章目錄前言一、函數賦值二、函數調用三、 代碼示例總結前言…

交叉編譯 手動安裝 SQLite 庫 移植ARM

# 下載源碼 wget https://www.sqlite.org/2023/sqlite-autoconf-3420000.tar.gz tar -xzf sqlite-autoconf-3420000.tar.gz cd sqlite-autoconf-3420000cd /home/lxh/sqlite-autoconf-3420000 make distclean //清除下&#xff0c;因為我安裝失敗過。 ./configure --hostarm-…

翻譯記憶庫(TMX)與機器翻譯的結合應用

更多內容請見: 機器翻譯修煉-專欄介紹和目錄 文章目錄 一、核心概念解析 1.1 翻譯記憶庫 (Translation Memory, TM) 1.2 翻譯記憶交換格式 (Translation Memory eXchange, TMX) 二、為何要將兩者結合? 2.1 TM和MT的優勢是高度互補的 2.2 TMX在結合中的關鍵作用 2.3 TMX與MT的…

SpringBoot中集成eclipse.paho.client.mqttv3實現mqtt客戶端并支持斷線重連、線程池高并發改造、存儲入庫mqsql和redis示例業務流程,附資源下載

場景 SpringBoot整合MQTT服務器實現消息的發送與訂閱(推送消息與接收推送)&#xff1a; SpringBoot整合MQTT服務器實現消息的發送與訂閱(推送消息與接收推送)_服務端接收mqtt消息-CSDN博客 上面SpringBoot集成MQTT使用的是spring-integration-mqtt依賴&#xff0c;也是經常使…

【考研408數據結構-08】 圖論基礎:存儲結構與遍歷算法

&#x1f4da; 【考研408數據結構-08】 圖論基礎&#xff1a;存儲結構與遍歷算法 &#x1f3af; 考頻&#xff1a;????? | 題型&#xff1a;選擇題、綜合應用題、算法設計題 | 分值&#xff1a;約8-15分 引言 想象你正在規劃一次跨省自駕游&#xff0c;面前攤開一張復雜的…

SQL查詢語句的執行順序

好的&#xff0c;我們來詳細講解一下 SQL 查詢語句的執行順序。 很多人會誤以為 SQL 的執行順序就是我們寫的順序&#xff08;SELECT -> FROM -> WHERE -> GROUP BY -> HAVING -> ORDER BY&#xff09;&#xff0c;但實際上&#xff0c;數據庫引擎在底層處理查詢…

【Android】OKHttp網絡請求原理和弱網優化

【Android】OKHttp網絡請求原理和弱網優化 1. OkHttp 網絡請求原理 OkHttp 的請求過程可以分為 四個關鍵階段&#xff1a; &#xff08;假設你是通過 OkHttpClient.newCall(request).enqueue(callback) 發的請求&#xff09; OkHttpClient│▼ Dispatcher (調度器)│▼ RealC…

概率論基礎教程第4章 隨機變量(四)

4.7 泊松隨機變量 定義 泊松隨機變量&#xff1a;如果一個取值于 $ 0, 1, 2, \ldots $ 的隨機變量對某一個 $ \lambda > 0 $&#xff0c;其分布列為&#xff1a; p(i)P{Xi}e?λλii!i0,1,2,?(7.1) \boxed{p(i) P\{X i\} e^{-\lambda} \frac{\lambda^i}{i!} \qquad i 0…

Unity高級開發:反射原理深入解析與實踐指南 C#

Unity高級開發&#xff1a;反射原理深入解析與實踐指南 在Unity游戲開發中&#xff0c;反射&#xff08;Reflection&#xff09; 是一項強大的元編程技術&#xff0c;它允許程序在運行時動態地獲取類型信息、創建對象和調用方法。根據Unity官方統計&#xff0c;超過78%的商業游…

任務五 推薦頁面功能開發

一、推薦頁面需求分析 由推薦頁面效果圖,可以看出,推薦頁面主要由頂部輪播圖和歌單列表頁面組成 二、推薦頁面輪播圖組件封裝 由于輪播圖,可能在項目多個地方用到,因此可以將輪播圖抽調成一個組件,然后各個頁面調用這個組件。 在開發輪播圖組件時,需要安裝better-scro…

【工具使用-Docker容器】構建自己的鏡像和容器

1. 鏡像和容器介紹 鏡像&#xff08;Image&#xff09;是一個只讀的模板&#xff0c;包含了運行某個應用所需的全部內容&#xff0c;比如&#xff1a; 操作系統&#xff08;比如 Ubuntu&#xff09;應用程序代碼運行環境&#xff08;如 Python、Java、Node.js 等&#xff09;庫…

Apache Shiro550 漏洞(CVE-2016-4437):原理剖析與實戰 SOP

在 Web 安全領域&#xff0c;反序列化漏洞一直是威脅等級極高的存在&#xff0c;而 Apache Shiro 框架中的 Shiro550 漏洞&#xff08;CVE-2016-4437&#xff09;&#xff0c;更是因利用門檻低、影響范圍廣&#xff0c;成為滲透測試中頻繁遇到的經典漏洞。本文將從 “原理拆解”…

安卓開發者自學鴻蒙開發3持久化/數據與UI綁定

AppStorage,PersistentStorage與StorageLink AppStorage是應用全局狀態管理器,數據存儲于內存中,常見的如全局的黑暗模式,StorageLink是用來綁定AppStorage的鍵到ui上的工具,省去了用戶手寫代碼的無聊過程,PersistentStorage可以綁定AppStorage的鍵,自動持久化到磁盤,同時支持多…

GitHub宕機生存指南:從應急協作到高可用架構設計

GitHub宕機生存指南&#xff1a;從應急協作到高可用架構設計 摘要&#xff1a; GitHub作為全球開發者的協作中心&#xff0c;其服務穩定性至關重要。然而&#xff0c;任何在線服務都無法保證100%的可用性。本文深入探討了當GitHub意外宕機時&#xff0c;開發團隊應如何應對。我…

機器學習算法篇(十三)------詞向量轉化的算法思想詳解與基于詞向量轉換的文本數據處理的好評差評分類實戰(NPL基礎實戰)

目錄 一、詞向量原理介紹 (1). 詞向量的核心概念 (2). 傳統文本表示的局限性 1. 獨熱編碼&#xff08;One-Hot Encoding&#xff09; 2. 詞袋模型&#xff08;Bag of Words&#xff09; 3. TF-IDF (3). 詞向量的核心原理 (4). 主流詞向量模型 1. Word2Vec&#xff08;20…

JS自定義函數(2)

1. 變量的作用域全局變量定義&#xff1a;在函數外聲明的變量作用范圍&#xff1a;在整個JS文檔中生效生命周期&#xff1a;頁面關閉時銷毀局部變量定義&#xff1a;在函數內用 var 聲明的變量作用范圍&#xff1a;只能在函數內部使用生命周期&#xff1a;函數執行完畢時銷毀作…

【數據集】Argoverse 數據集:自動駕駛研究的強大基石

Argoverse數據集&#xff1a;自動駕駛研究的強大基石 在自動駕駛技術蓬勃發展的當下&#xff0c;高質量的數據集對于推動相關算法研究和模型訓練起著舉足輕重的作用。Argoverse 數據集便是其中的佼佼者&#xff0c;它為自動駕駛領域的眾多任務提供了豐富且優質的數據資源。 一、…

--- 哈希表和哈希沖突 ---

哈希&#xff08;散列&#xff09;方法是對插入的數據通過哈希函數計算出一個哈希地值&#xff0c;并將這個哈希地址作為儲存改數據的地址&#xff0c;這樣下次再查找這個數據時&#xff0c;只需要通過哈希函數再獲取到該地址然后直接去拿就好這樣就做到了不經過任何比較&#…