Automate Grafana Dashboard Snapshots & Email Reports with Puppeteer
This guide explains how to use Node.js, Puppeteer, and Nodemailer to capture Grafana panel images, generate email reports, and schedule automated deliveries, covering environment setup, code modules, screenshot techniques, font handling, and optional cron integration for continuous monitoring.
In operational workflows, there is often a need to send chart images via email for monitoring or reporting, but open‑source tools like Zabbix, Grafana, and Kibana lack flexible image generation. Using Node.js with Puppeteer and Nodemailer, we built a solution that captures Grafana panels and emails them as reports.
Principle
Puppeteer is a Node.js library that provides a high‑level API to control headless Chrome or Chromium via the DevTools Protocol. It can run in headless mode or with a visible UI.
Headless browsers such as Selenium, PhantomJS, and Puppeteer are commonly used for web automation, testing, and scraping. Puppeteer excels at rendering dynamic pages, making it ideal for capturing Grafana charts that are generated client‑side.
For more information on the DevTools Protocol, see https://chromedevtools.github.io/devtools-protocol/.
Prepare Environment
CentOS 7.6
Node.js 14.3.0
cnpm 6.1.1
<code>cd /opt
wget https://cdn.npm.taobao.org/dist/node/v14.3.0/node-v14.3.0-linux-x64.tar.xz
tar -xf node-v14.3.0-linux-x64.tar.xz
vi /etc/profile
# set for nodejs
export NODE_HOME=/opt/node-v14.3.0-linux-x64
export PATH=$NODE_HOME/bin:$PATH
source /etc/profile
npm config set registry=http://registry.npm.taobao.org
npm install -g cnpm --registry=https://registry.npm.taobao.org
# screenshot storage directory
mkdir /tmp/png/
# project directory
mkdir /opt/GrafanaSnapProject
cd /opt/GrafanaSnapProject
cnpm i --save puppeteer
cnpm i --save nodemailer</code>Source Logic
We split the functionality into two modules:
mailPush.jsfor sending emails and
getPicture.jsfor capturing screenshots.
The email module requires a unique content ID for each attachment so the images can be referenced inside the HTML body.
String concatenation in Node.js can be done with template literals, e.g.
${str1}${str2} hello.
<code>/*
function: send pictures in mail's html content via nodejs
author: [email protected]
*/
const nodemailer = require('nodemailer');
let transporter = nodemailer.createTransport({
host: 'mail.exchangehost.com',
port: 587,
secure: false,
auth: {
user: 'yourEmail',
pass: 'yourPassword'
},
tls: { rejectUnauthorized: false }
});
function sendMymail(who, subject, title) {
let mailOptions = {
from: '"autoreport" <[email protected]>',
to: who,
subject: subject,
html: `<h2 align="center" style="color:red;font-size:24px">${title}</h2><br>
<h5 align="center" style="color:green;font-size:20px">Report 1</h5><br>
<img src="cid:001"/><br>
<h5 align="center" style="color:green;font-size:20px">Report 2</h5><br>
<img src="cid:002"/>`,
attachments: [
{ filename: '001.png', path: '/tmp/png/001.png', cid: '001' },
{ filename: '002.png', path: '/tmp/png/002.png', cid: '002' }
]
};
transporter.sendMail(mailOptions, (error, info) => {
if (error) return console.log(error);
console.log('Message %s sent: %s', info.messageId, info.response);
});
}
exports.sendMymail = sendMymail;</code>Puppeteer runs well on Linux without a graphical desktop, but you need to install Chinese fonts to avoid garbled characters in screenshots.
Popular Chinese fonts include Fangsong, SimSun, and Alibaba's free commercial fonts, which can be downloaded from https://alibabafont.taobao.com/.
<code>yum -y install fontconfig
cd /usr/share/fonts
mkdir chinese
cd chinese/
# upload fonts, e.g., Fangsong.ttf
chmod -R 775 /usr/share/fonts/chinese
yum -y install ttmkfdir
ttmkfdir -e /usr/share/X11/fonts/encodings/encodings.dir
vi /etc/fonts/fonts.conf
# add <dir>/usr/local/share/fonts/chinese</dir> to the config
fc-list :lang=zh</code>The
getPicture.jsmodule logs into Grafana, navigates to specific panels, captures screenshots, and then calls the email module.
Locating and clicking buttons can be tedious; the Puppeteer Recorder Chrome extension helps generate the necessary selectors.
Example of using the recorder to capture a panel’s menu:
Analyzing a Grafana dashboard reveals many panels; each panel’s dropdown menu provides a “View” option that can be recorded and scripted.
<code>/*
function: snapshot grafana panel pictures via puppeteer
author: [email protected]
*/
function sendPicture(){
const sendModule = require('./mailPush.js');
sendModule.sendMymail('[email protected],[email protected]','Weekly Report','^_^ OpenStack Monitoring Report');
}
async function getPicture(){
const puppeteer = require('puppeteer');
const account = `zuoguocai`;
const password = `xxxxxx`;
const browser = await puppeteer.launch({args: ['--no-sandbox', '--disable-setuid-sandbox']});
const page = await browser.newPage();
await page.setViewport({width:1827, height:979});
await page.goto('https://yourgrafana.com');
await page.type('input[type="text"]', account);
await page.type('#inputPassword', password);
await page.click('button[type="submit"]');
await page.waitForNavigation({waitUntil: 'load'});
await page.goto('https://yourgrafana.com/d/-a3b-ddWz/hu-lian-wang-chu-kou-hui-zong?refresh=30s&orgId=1');
await page.waitFor(1000);
// Capture panel 19
await page.waitForSelector('#panel-19 .fa:nth-child(1)');
await page.click('#panel-19 .fa:nth-child(1)');
await page.waitForSelector('.open .dropdown-menu li:nth-child(1) a .dropdown-item-text');
await page.click('.open .dropdown-menu li:nth-child(1) a .dropdown-item-text');
await page.waitFor(1000);
await page.screenshot({path: '/tmp/png/001.png'});
// Capture panel 2
await page.goto('https://yourgrafana.com/d/-a3b-ddWz/hu-lian-wang-chu-kou-hui-zong?refresh=30s&orgId=1');
await page.waitFor(1000);
await page.waitForSelector('#panel-2 .fa:nth-child(1)');
await page.click('#panel-2 .fa:nth-child(1)');
await page.waitForSelector('.open .dropdown-menu li:nth-child(1) a .dropdown-item-text');
await page.click('.open .dropdown-menu li:nth-child(1) a .dropdown-item-text');
await page.waitFor(1000);
await page.screenshot({path: '/tmp/png/002.png'});
await browser.close();
await sendPicture();
}
getPicture();</code>Source code repository: https://github.com/ZuoGuocai/GrafanaSnapProject
Test Sending Effect
Run the script:
<code>node getPicture.js</code>Then check your email client for the generated report.
About Scheduling and Grafana Time Range
The time range can be appended directly to the Grafana URL. Scheduling requires a custom cron job or a library such as gocron (https://github.com/ouqiang/gocron).
Puppeteer’s capabilities extend beyond screenshots to PDF generation and full‑stack front‑end testing, offering many possibilities for further automation.
Efficient Ops
This public account is maintained by Xiaotianguo and friends, regularly publishing widely-read original technical articles. We focus on operations transformation and accompany you throughout your operations career, growing together happily.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.