Skip to content

Instantly share code, notes, and snippets.

@Yusseiin
Last active November 3, 2023 13:08
Show Gist options
  • Save Yusseiin/ee2595dab8e67460219bcc62dec46a29 to your computer and use it in GitHub Desktop.
Save Yusseiin/ee2595dab8e67460219bcc62dec46a29 to your computer and use it in GitHub Desktop.
Grafana dashboard export in PDF

Grafana dashboard export in PDF

Thanks to @dumy for it's guides you can find it here:
https://flows.nodered.org/flow/482d3b2c9736fc1544dd62b50778d8d9
I have made some changes to export the page in "Dark mode" and i have added some "tutorials" for Puppeteer

Requisites:
-node-red-contrib-string - https://flows.nodered.org/node/node-red-contrib-string
-Puppeteer
This can be installed with

npm i puppeteer 

Than you have to go to your node-red installation folder and change settings.js and add this line

puppeteer:require('puppeteer')

under "functionGlobalContext"

Build Status

Setup:

In setting dashboard go to the module link and set link from Node-Red ❤️ N.B. Remeber to flag "Include current time range"

http://localhost:1880/grafana/${__dashboard.uid}/${__dashboard.name}

Change "localhost:1880" with your ip/dns

Build Status

Resulted:

Build Status

When you click the button this flow will be executed:

immagine

In the node function there are 3 parameters that needs to be change:

// URL to load should be passed as first parameter const url = 'http://localhost:3000/d/' + msg.req.params.uid + '/' + msg.req.params.name +'?orgId=1&kiosk&from=' + msg.payload.from + '&to=' + msg.payload.to;

// Username and password (with colon separator) should be second parameter const auth_string = 'username:password!';

// Output file name should be third parameter const outfile = '';

Build Status

After call the link resulted in pdf

Build Status

it is a simple method to export grafana pages in pdf,

Thank you ✌️

[
{
"id": "39737d292e7049b0",
"type": "tab",
"label": "Flow 2",
"disabled": false,
"info": "",
"env": []
},
{
"id": "1dd5c082c89e4ce8",
"type": "change",
"z": "39737d292e7049b0",
"name": "Set Headers",
"rules": [
{
"t": "set",
"p": "headers",
"pt": "msg",
"to": "{}",
"tot": "json"
},
{
"t": "set",
"p": "headers.content-type",
"pt": "msg",
"to": "application/pdf",
"tot": "str"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 850,
"y": 220,
"wires": [
[
"c33065c85ad230e5"
]
]
},
{
"id": "c33065c85ad230e5",
"type": "http response",
"z": "39737d292e7049b0",
"name": "",
"x": 1030,
"y": 220,
"wires": []
},
{
"id": "0450c51d52c34862",
"type": "debug",
"z": "39737d292e7049b0",
"name": "debug 34",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 420,
"y": 180,
"wires": []
},
{
"id": "99e0ba3034fd43bc",
"type": "string",
"z": "39737d292e7049b0",
"name": "replace",
"methods": [
{
"name": "replaceAll",
"params": [
{
"type": "str",
"value": " "
},
{
"type": "str",
"value": "-"
}
]
},
{
"name": "toLowerCase",
"params": []
}
],
"prop": "req.params.name",
"propout": "req.params.name",
"object": "msg",
"objectout": "msg",
"x": 420,
"y": 220,
"wires": [
[
"02673b9ff34538fe"
]
]
},
{
"id": "02673b9ff34538fe",
"type": "function",
"z": "39737d292e7049b0",
"name": "exportToPDFDark",
"func": "// @ts-nocheck\n\n// const puppeteer = require('puppeteer');\nconst puppeteer = global.get(\"puppeteer\");\n// const odbc = global.get(\"odbc\");\n\n// URL to load should be passed as first parameter\nconst url = 'http://localhost:3000/d/' + msg.req.params.uid + '/' + msg.req.params.name +'?orgId=1&kiosk&from=' + msg.payload.from + '&to=' + msg.payload.to;\n// Username and password (with colon separator) should be second parameter\n\nconst auth_string = 'username:password';\n// Output file name should be third parameter\nconst outfile = '' + msg.req.params.uid+'.pdf';\n\n\n// TODO: Output an error message if number of arguments is not right or arguments are invalid\n\n// Set the browser width in pixels. The paper size will be calculated on the basus of 96dpi,\n// so 1200 corresponds to 12.5\".\nconst width_px = 1920;\n// Note that to get an actual paper size, e.g. Letter, you will want to *not* simply set the pixel\n// size here, since that would lead to a \"mobile-sized\" screen (816px), and mess up the rendering.\n// Instead, set e.g. double the size here (1632px), and call page.pdf() with format: 'Letter' and\n// scale = 0.5.\n\n// Generate authorization header for basic auth\n// @ts-ignore\nconst auth_header = 'Basic ' + new Buffer.from(auth_string).toString('base64');\n\n\n const browser = await puppeteer.launch({headless:\"new\"});\n const page = await browser.newPage();\n\n // Set basic auth headers\n await page.setExtraHTTPHeaders({ 'Authorization': auth_header });\n\n // Increase timeout from the default of 30 seconds to 120 seconds, to allow for slow-loading panels\n await page.setDefaultNavigationTimeout(1200000);\n\n // Increasing the deviceScaleFactor gets a higher-resolution image. The width should be set to\n // the same value as in page.pdf() below. The height is not important\n await page.setViewport({\n width: width_px,\n height: 5000,\n deviceScaleFactor: 2,\n isMobile: false\n })\n\n // Wait until all network connections are closed (and none are opened withing 0.5s).\n // In some cases it may be appropriate to change this to {waitUntil: 'networkidle2'},\n // which stops when there are only 2 or fewer connections remaining.\n await page.goto(url, { waitUntil: 'networkidle0' });\n\n // Hide all panel description (top-left \"i\") pop-up handles and, all panel resize handles\n // Annoyingly, it seems you can't concatenate the two object collections into one\n await page.evaluate(() => {\n let infoCorners = document.getElementsByClassName('panel-info-corner');\n for (el of infoCorners) { el.hidden = true; };\n let resizeHandles = document.getElementsByClassName('react-resizable-handle');\n for (el of resizeHandles) { el.hidden = true; };\n });\n \n // Get the height of the main canvas, and add a margin\n var height_px = 1200;\n height_px = await page.evaluate(() => {\n return document.getElementsByClassName('react-grid-layout')[0].getBoundingClientRect().bottom;\n }) + 20;\n \n await page.addStyleTag({\n content: `\n html {\n -webkit-print-color-adjust: exact !important;\n -webkit-filter: opacity(1) !important;\n }\n `\n });\n \n const pdf = await page.pdf({\n // path: outfile,\n width: width_px + 'px',\n height: height_px + 'px',\n // format: 'A4',\n // landscape: true,\n // format: 'Letter', <-- see note above for generating \"paper-sized\" outputs\n scale: 1,\n displayHeaderFooter: false,\n margin: {\n top: 0,\n right: 0,\n bottom: 0,\n left: 0,\n },\n });\n \n await browser.close();\nmsg.topic = url;\nmsg.payload = pdf;\nreturn msg;",
"outputs": 1,
"timeout": "",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 630,
"y": 220,
"wires": [
[
"1dd5c082c89e4ce8"
]
]
},
{
"id": "895671cc7f376f8c",
"type": "http in",
"z": "39737d292e7049b0",
"name": "",
"url": "/grafana/:uid/:name",
"method": "get",
"upload": false,
"swaggerDoc": "",
"x": 190,
"y": 220,
"wires": [
[
"99e0ba3034fd43bc",
"0450c51d52c34862"
]
]
}
]
@Yusseiin
Copy link
Author

Yusseiin commented Nov 3, 2023

It seems correct for me.
immagine
I think that maybe is something to do with docker installation

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment