Minimales Beispiel
Ein Minimales setup besteht aus 2 Kernbestandteilen
- Ein Script im Frontend.
- Ein Flow im Node Red.
Folgende flyze.services müssen für ein Reporting laufen
| Service | Beschreibung |
|---|---|
| ConnectorApi | Wird als Gateway genutzt, um die Reporting Api zu erreichen. |
| ReportingApi | Generiert den PDF Report. |
| NodeRed | NodeRed ist kein muss, aber hilfreich um die Reporting Api zu erreichen. |
1. Scripting im Frontend
Folgend ein Action Script welches auf ein Button gelegt wurde um ein Report zu generieren. In diesem Script können
reportName, reportRoute, reportData angepasst werden um mit dem nachfolgend aufgeführten NodeRed Flow zu interagieren.
| Parameter | Beschreibung |
|---|---|
| reportName | Kann frei gewählt werden. |
| reportRoute | Muss mit dem NodeRed Enpoint übereinstimmen |
| reportData | Stelle die Daten bereit, die beim generieren des Reports verwendet werden |
const CONNECTOR_API_URL = fyzPlatform.env.get().config.connectorApiUrl;
// Set The NodeRed Endpoint you have chosen
const reportRoute = 'generateReport';
// Set the file name you want the user to have when downloading
const reportDate = "2025-09-01"
const reportName = `${reportDate}_report`;
// Set the date you want to use inside the endpoint
const reportData = {
test: "1234",
testArray: [
{ id: `1`, name: "content1" },
{ id: `2`, name: "content2" }
]
}
// execute the report
generateReport(reportRoute, reportName, reportData);
// helper function - call the node red endpoint
function generateReport(route, name, data) {
fyzUtils.httpApi
.executeRequestsAsync([{
action: 'POST',
url: CONNECTOR_API_URL + '/api/data/connect',
body: {
Configuration: {
Endpoint: 'FLYZE_NODE_RED',
Route: `${route}`,
Authentication: false
},
ApiConfig: data
},
responseType: "arraybuffer"
}], (res) => {
if (res && res[0]) {
makeBrowserDownload(res[0], name);
}
})
}
// helper function - download the file to browser
function makeBrowserDownload(fileData, fileName) {
// Create a blob from the response
const blob = new Blob([fileData], { type: "application/pdf" });
// Create a temporary download link
const url = window.URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = `${fileName}.pdf`; // filename
document.body.appendChild(a);
a.click();
// Clean up
setTimeout(() => {
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
}, 0);
}
1. Flow im NodeRed
Damit das obige Script funktioniert, kann folgender NodeRed Flow verwendet werden:
Der Flow kann an den passenden stellen so angepasst werden, das ein Bericht so generiert wird, wie gewünscht. Dazu können in den Funktionen
Logo, Report Data, Page Header, PDF Body, und Page Footer die Werte angepasst werden.
| Funktion | Beschreibung |
|---|---|
| Logo | Hier kann das Logo in .svg angegeben werden. |
| Report Data | Hier können die Daten die im Report verwendet werden, deklariert werden. |
| Page Header | Hier kann das Aussehen der Seitenkopfzeile definiert werden. |
| PDF BODY | Hier kann das Aussehen des PDFs definiert werden. |
| Page Footer | Hier kann das Aussehen der Seitenfußzeile definiert werden. |
[
{
"id": "26ecfdaf11c9fd26",
"type": "tab",
"label": "Test Report",
"disabled": false,
"info": "",
"env": []
},
{
"id": "30185f1ee6edfa2a",
"type": "http in",
"z": "26ecfdaf11c9fd26",
"name": "[/generateReport] Endpoint",
"url": "/generateReport",
"method": "post",
"upload": false,
"skipBodyParsing": false,
"swaggerDoc": "",
"x": 310,
"y": 540,
"wires": [
[
"9eaf35c32f79644b"
]
]
},
{
"id": "dc1d843cef0e8697",
"type": "http request",
"z": "26ecfdaf11c9fd26",
"name": "Send Request to Reporting Api",
"method": "POST",
"ret": "bin",
"paytoqs": "ignore",
"url": "http://reporting-api:8080/export",
"tls": "",
"persist": false,
"proxy": "",
"insecureHTTPParser": false,
"authType": "",
"senderr": false,
"headers": [],
"x": 1530,
"y": 540,
"wires": [
[
"5cad433444728aa4"
]
]
},
{
"id": "5cad433444728aa4",
"type": "http response",
"z": "26ecfdaf11c9fd26",
"name": "",
"statusCode": "",
"headers": {},
"x": 1750,
"y": 540,
"wires": []
},
{
"id": "e2667eff7ac96456",
"type": "function",
"z": "26ecfdaf11c9fd26",
"name": "Generate Report Configuration",
"func": "const reportData = msg.payload.reportData;\nconst reportTempate = msg.payload.reportTemplate;\n\nmsg.payload = {\n template: reportTempate.body,\n data: JSON.stringify(reportData),\n marginOptions: {\n top: \"5mm\",\n bottom: \"5mm\",\n left: \"10mm\",\n right: \"10mm\"\n },\n headerFooterOptions: {\n \"setHeaderFooter\": true,\n \"headerTemplate\": reportTempate.header,\n \"footerTemplate\": reportTempate.footer\n },\n Landscape: false,\n config: {\n dynamic: true,\n type: `PDF`,\n typeConfig: {\n filename: \"Check Car Report.pdf\"\n }\n }\n}\n\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1230,
"y": 540,
"wires": [
[
"dc1d843cef0e8697"
]
]
},
{
"id": "9eaf35c32f79644b",
"type": "function",
"z": "26ecfdaf11c9fd26",
"name": "Logo",
"func": "const myLogo = `<svg width=\"121\" height=\"117\" viewBox=\"0 0 121 117\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M38.1709 10.1491C16.7402 17.5182 17.9957 39.5376 17.9957 39.5376H8V56.5897H17.9077V108.309H35.652V56.5897H45.4757V39.5376H35.652V39.174C35.692 37.3197 36.3237 29.2512 44.2363 26.3859C47.079 25.3549 50.8534 24.9872 55.8792 25.8184V108.309H73.5715V12.9984C73.5515 12.9984 73.5915 12.9984 73.5715 12.9984V10.0652C57.6824 7.01202 46.3074 7.60347 38.1709 10.1491Z\" fill=\"black\"/>\n<path d=\"M112.743 84.6641H89.0973V108.294H112.743V84.6641Z\" fill=\"#0F6FFF\"/>\n</svg>\n`;\n\nmsg.payload = {\n ...msg.payload,\n logo: myLogo\n}\n\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 850,
"y": 540,
"wires": [
[
"fc909feaa1445631"
]
]
},
{
"id": "7101b4b5b9b53c8d",
"type": "comment",
"z": "26ecfdaf11c9fd26",
"name": "Endpoint Of Your Report",
"info": "# Test With:\n\nSyntax: `nodered-domain`/`endpointName`\nExample: `nodered.sub1.flyze.io/generateReport`",
"x": 310,
"y": 500,
"wires": []
},
{
"id": "b6dd5751c0789aef",
"type": "function",
"z": "26ecfdaf11c9fd26",
"name": "Page Footer",
"func": "const footerTemplate = `\n<style>\n /* Footer Styles */\n .page-footer {\n font-size: 8px;\n width: 100%;\n padding: 0 10mm;\n \n display: flex;\n flex-direction: row;\n justify-content: space-between;\n align-items: center;\n }\n .page-footer-logo {\n margin-right: 24px;\n }\n .page-footer-print-info {\n text-align: right;\n flex-grow: 1;\n }\n</style>\n<div class=\"page-footer\">\n <div class=\"page-footer-logo\">\n ${msg.payload.logo}\n </div>\n <div class=\"page-footer-print-info\">\n ${new Date().toLocaleDateString()}\n </div>\n</div>\n`\n\nmsg.payload = {\n ...msg.payload,\n reportTemplate: {\n ...msg.payload.reportTemplate,\n footer: footerTemplate\n }\n}\n\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 870,
"y": 780,
"wires": [
[
"e2667eff7ac96456",
"f5285380de5542c3"
]
]
},
{
"id": "e8364391bb625c9e",
"type": "function",
"z": "26ecfdaf11c9fd26",
"name": "Page Header",
"func": "const headerTemplate = `\n<style>\n .page-header {\n font-size: 8px;\n width: 100%;\n padding: 0 10mm;\n\n display: flex;\n flex-direction: row;\n justify-content: space-between;\n }\n\n #matrixNumber{\n margin: 0;\n }\n\n #system{\n margin: 0 0 0.5rem 0;\n }\n\n #description{\n \n }\n \n #tuev-info{\n font-size: 12px;\n color: red;\n align-self: center;\n }\n</style>\n<div class=\"page-header\">\n <div>\n <h3 id=\"matrixNumber\">\n FOO\n </h3>\n <h4 id=\"system\">\n BAR\n </h4>\n <p id=\"description\">\n BATZ\n </p>\n </div>\n <div id=\"tuev-info\">\n Die rot gekennzeichneten Punkte müssen alle 2 Jahre vom BB-TÜV überprüft werden<br />\n Bezug auf Vorschrift <a href=\"https://www.umwelt-online.de/recht/t_regeln/trb/trb800/801_26.htm#:~:text=1.1%20Diese%20TRB%20801%20Nr,insoweit%20den%20anderen%20TRB%20vor.\" target=\"_blank\">BetrSichV TRB801 Nr. 25</a>\n </div>\n <div>Seite <span class=\"pageNumber\"></span> von <span class=\"totalPages\"></span></div>\n</div>\n`;\n\nmsg.payload = {\n ...msg.payload,\n reportTemplate: {\n ...msg.payload.reportTemplate,\n header: headerTemplate\n }\n}\n\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 870,
"y": 660,
"wires": [
[
"b4421ea9f1efe6b1"
]
]
},
{
"id": "b4421ea9f1efe6b1",
"type": "function",
"z": "26ecfdaf11c9fd26",
"name": "PDF BODY",
"func": "const data = msg.payload;\n\nconst styles = `\n html {\n -webkit-print-color-adjust: exact;\n font-family: \"Calibri\", sans-serif;\n }\n .page-footer-logo {\n /*background-color: #ff44ff;\n margin: 16px;\n padding: 16px;\n */\n }\n\n body { \n /*background-color: #ff44ff;*/\n }\n`;\n\nconst content = `\n<div>\n Show If value of test equals 123, else show [No Content]\n ${data.test === \"123\" ? data.test : \"No Content\"}\n</div>\n\n<div ngFor=\"let test of testArray\" style=\"page-break-after: always;\">\n <div>Id: {{test.id}}</div>\n <div>Name: {{test.name}}</div>\n</div>\n\n<div class=\"page-footer-logo\" style=\"margin-right: 24px;\">\n ${data.logo}\n</div>\n<div class=\"page-footer-logo\" style=\"margin-right: 24px;\">\n {{logo}}\n</div>\n`;\n\nconst htmlTemplate = `\n<html>\n <title>Test</title>\n <style>\n ${styles}\n </style>\n\n <body>\n ${content}\n </body>\n</html>`\n\nmsg.payload = {\n ...msg.payload,\n reportTemplate: {\n ...msg.payload.reportTemplate,\n body: htmlTemplate\n }\n}\n\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 870,
"y": 720,
"wires": [
[
"b6dd5751c0789aef"
]
]
},
{
"id": "037ad02b188af2e4",
"type": "comment",
"z": "26ecfdaf11c9fd26",
"name": "Change Functions to Design PDF",
"info": "",
"x": 930,
"y": 500,
"wires": []
},
{
"id": "93960a29cd64e284",
"type": "comment",
"z": "26ecfdaf11c9fd26",
"name": "Scripting Example",
"info": "# Call from Scripting\n\n``` javascript\nconst CONNECTOR_API_URL = fyzPlatform.env.get().config.connectorApiUrl;\n\n// Set The NodeRed Endpoint you have chosen \nconst reportRoute = 'generateReport';\n\n// Set the file name you want the user to have when downloading\nconst reportDate = \"2025-09-01\"\nconst reportName = `${reportDate}_report`;\n\n// Set the date you want to use inside the endpoint\nconst reportData = {\n test: \"1234\",\n testArray: [\n { id: `1`, name: \"content1\" },\n { id: `2`, name: \"content2\" }\n ]\n}\n\n// execute the report\ngenerateReport(reportRoute, reportName, reportData);\n\n\n// helper function - call the node red endpoint\nfunction generateReport(route, name, data) {\n fyzUtils.httpApi\n .executeRequestsAsync([{\n action: 'POST',\n url: CONNECTOR_API_URL + '/api/data/connect',\n body: {\n Configuration: {\n Endpoint: 'FLYZE_NODE_RED',\n Route: `${route}`,\n Authentication: false\n },\n ApiConfig: data\n },\n responseType: \"arraybuffer\"\n }], (res) => {\n if (res && res[0]) {\n makeBrowserDownload(res[0], name);\n }\n })\n}\n\n// helper function - download the file to browser\nfunction makeBrowserDownload(fileData, fileName) {\n // Create a blob from the response\n const blob = new Blob([fileData], { type: \"application/pdf\" });\n\n // Create a temporary download link\n const url = window.URL.createObjectURL(blob);\n const a = document.createElement(\"a\");\n a.href = url;\n a.download = `${fileName}.pdf`; // filename\n document.body.appendChild(a);\n a.click();\n\n // Clean up\n setTimeout(() => {\n document.body.removeChild(a);\n window.URL.revokeObjectURL(url);\n }, 0);\n}\n```",
"x": 290,
"y": 440,
"wires": []
},
{
"id": "fc909feaa1445631",
"type": "function",
"z": "26ecfdaf11c9fd26",
"name": "Report Data",
"func": "\nmsg.payload = {\n reportData: {\n ...msg.payload\n }\n}\n\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 870,
"y": 600,
"wires": [
[
"e8364391bb625c9e"
]
]
},
{
"id": "b1119e7cbcccb861",
"type": "comment",
"z": "26ecfdaf11c9fd26",
"name": "Report Configuration",
"info": "",
"x": 1190,
"y": 500,
"wires": []
},
{
"id": "95a29bd092ef79ae",
"type": "comment",
"z": "26ecfdaf11c9fd26",
"name": "Add a logo",
"info": "",
"x": 720,
"y": 540,
"wires": []
},
{
"id": "89c1f931f118ec0b",
"type": "comment",
"z": "26ecfdaf11c9fd26",
"name": "Pass Data",
"info": "",
"x": 720,
"y": 600,
"wires": []
},
{
"id": "23765e2d90ba3882",
"type": "comment",
"z": "26ecfdaf11c9fd26",
"name": "Define Header Design",
"info": "",
"x": 680,
"y": 660,
"wires": []
},
{
"id": "67a0070037431d40",
"type": "comment",
"z": "26ecfdaf11c9fd26",
"name": "Define Footer Design",
"info": "",
"x": 680,
"y": 780,
"wires": []
},
{
"id": "4356b43a505d4ba7",
"type": "comment",
"z": "26ecfdaf11c9fd26",
"name": "Define PDF Design",
"info": "",
"x": 690,
"y": 720,
"wires": []
},
{
"id": "f5285380de5542c3",
"type": "debug",
"z": "26ecfdaf11c9fd26",
"name": "debug 4",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "false",
"statusVal": "",
"statusType": "auto",
"x": 1160,
"y": 600,
"wires": []
}
]
