Operations 14 min read

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.

Efficient Ops
Efficient Ops
Efficient Ops
Automate Grafana Dashboard Snapshots & Email Reports with Puppeteer
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.js

for sending emails and

getPicture.js

for 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.js

module 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.

PuppeteerOperationsNode.jsGrafanaScreenshotEmail Automation
Efficient Ops
Written by

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.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.