gapitio / gapit-htmlgraphics-panel

Grafana panel for displaying metric sensitive HTML or SVG graphics.
https://gapit-htmlgraphics-panel.gapit.io/
MIT License
65 stars 8 forks source link

external js script are loaded but not recognized #190

Open DonatoD opened 5 months ago

DonatoD commented 5 months ago

Using htmlgraphics panel, from grafana10.4 to 11 several js script, even loaded locally, are yes correctly loaded but then the object itself is not recognized. The problem is that this is happening with several script, pdf-lib.min.js just to name one.

You can reproduce it by loading this script https://cdn.jsdelivr.net/npm/pdf-lib/dist/pdf-lib.min.js into the oninit section and check for the PDFLib object. It will be undefined

ZuperZee commented 5 months ago

Could you provide an example with the panel options, panel json or dashboard json?

Here's how to copy the panel options https://gapit-htmlgraphics-panel.gapit.io/docs/guides/how-to-import-export/#copy-from-one-panel-to-another. Here's how to get the dashboard json https://grafana.com/docs/grafana/latest/dashboards/share-dashboards-panels/#export-a-dashboard-as-json

DonatoD commented 5 months ago

{ "calcsMutation": "standard", "reduceOptions": { "calcs": [ "lastNotNull", "last", "firstNotNull", "first", "min", "max", "mean", "sum", "count", "range", "delta", "step", "diff", "logmin", "allIsZero", "allIsNull", "diffperc" ] }, "add100Percentage": false, "centerAlignContent": false, "overflow": "visible", "useGrafanaScrollbar": false, "SVGBaseFix": true, "codeData": "", "rootCSS": "", "css": "", "html": "", "renderOnMount": true, "onRender": "", "panelupdateOnMount": true, "dynamicHtmlGraphics": false, "dynamicData": false, "dynamicFieldDisplayValues": false, "dynamicProps": false, "onInitOnResize": false, "onInit": "var scriptList = [\n\t{id:\"PDFscriptA\",url:\"https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.14.305/pdf.min.js\"},\n\t{id:\"PDFscriptB\",url:\"https://cdn.jsdelivr.net/npm/pdf-lib/dist/pdf-lib.min.js\"},\n\t{id:\"MQTTscriptA\",url:\"https://cdnjs.cloudflare.com/ajax/libs/paho-mqtt/1.1.0/paho-mqtt.min.js\"},\n\t{id:\"MQTTscriptB\",url:\"https://unpkg.com/mqtt/dist/mqtt.min.js\"},\n\t{id:\"myGlobalScript\",text:\"var myGlobal = 33;\"}];\nloadScripts(scriptList)\n\t\t.then(function(){\n\t\t\ttry{console.log(\"PDFa\",pdfjsLib);}catch(err){console.log(err);}\n\t\t\ttry{console.log(\"PDFb\",PDFLib);}catch(err){console.log(err);}\n\t\t\ttry{console.log(\"MQTTa\",Paho);}catch(err){console.log(err);}\n\t\t\ttry{console.log(\"MQTTb\",mqtt);}catch(err){console.log(err);}\n\t\t\tconsole.log(\"myGlobal = \",myGlobal);\n\t\t});\n\nfunction loadScripts(scriptList){\n\treturn new Promise(async function(resolve,reject){\n\t\tvar app;\n\t\tvar loaded = 0;\n\t\tfor(i in scriptList){\n\t\t\ttry{\n\t\t\t\tapp = await loadScript(scriptList[i]);\n\t\t\t\tconsole.log(app);\n\t\t\t\tloaded++;\n\t\t\t}catch(e){\n\t\t\t\tconsole.log(e);\n\t\t\t\treject('Script loaded: '+loaded+'/'+scriptList.length);\n\t\t\t}\n\t\t}\n\t\tresolve(\"OK\");\n\t});\n\tfunction loadScript(obj){\n\t\treturn new Promise((resolve,reject) =>{\n\t\t\tvar el = null;\n\t\t\tif(!obj.id) reject('Id missing!');\n\t\t\tel = document.getElementById(obj.id);\n\t\t\tif(el) reject('Script not loaded: existing ID -> '+ obj.id);\n\t\t\telse{\n\t\t\t\tel = document.createElement(\"script\");\n\t\t\t\tel.type = \"text/javascript\";\n\t\t\t\tif(obj.id) el.id = obj.id;\n\t\t\t\tif(obj.url) el.src = obj.url;\n\t\t\t\tif(obj.text){\n\t\t\t\t\tel.innerHTML = obj.text;\n\t\t\t\t\tresolve('Loaded OK -> '+ obj.id);\n\t\t\t\t}else{\n\t\t\t\t\tel.onload = () => resolve('Loaded OK -> '+ obj.id);\n\t\t\t\t\tel.onerror = () => reject('Loaded Error -> '+ obj.id);\n\t\t\t\t}\n\t\t\t\tdocument.head.appendChild(el);\n\t\t\t}\t\t\n\t\t});\n\t}\n}\n\n" }

DonatoD commented 5 months ago

as you can see in these pictures, in grafana 10.1.4 it works image in grafana 11 it doesn't image

ZuperZee commented 5 months ago

Hmm, not sure. You could try using a bundler like https://github.com/gapitio/htmlgraphics-html-bundler-template More info on the website https://gapit-htmlgraphics-panel.gapit.io/docs/projects/#bundlers. I haven't used global scripts much in Grafana, so I'm a bit unfamiliar with why they stopped working in v11. I saw that the mqtt has a variable called mqtt, but the pdf-lib doesn't. Might it be something the package is doing?

DonatoD commented 5 months ago

I tried with bundler, but not always I was able on fix it.

jackw commented 5 months ago

Loading external javascript within Grafana will always be tricky, especially if those scripts are placing objects on the global as new versions of Grafana or other plugins might interfere with those libraries or override them. If instead of creating script tags to load external code you use the same JS module loader Grafana uses (SystemJS) it will give back the packages objects allowing you to isolate your code from the outside world. It is worth noting that SystemJS is part of Grafana and is likely to receive updates across versions of Grafana which might require you to update your loading code. Below is an example of how this could be done based on the above onInit example.

const scriptsToLoad = [
  {
    id: "pdfjs",
    url: "https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.14.305/pdf.js",
  },
  {
    id: "pdflib",
    url: "https://cdn.jsdelivr.net/npm/pdf-lib/dist/pdf-lib.min.js",
  },
  {
    id: "paho-mqtt",
    url: "https://cdnjs.cloudflare.com/ajax/libs/paho-mqtt/1.1.0/paho-mqtt.min.js",
  },
  {
    id: "mqtt",
    url: "https://ga.system.jspm.io/npm:mqtt@5.7.0/dist/mqtt.esm.js",
  },
];

const loadedScripts = {}

loadScripts(scriptsToLoad).then(() => {
  console.log("onInit", "scripts loaded!!!", loadedScripts);
  console.log(`pdfjs version: ${loadedScripts.pdfjs.version}`);
});

async function loadScripts(scriptList) {
  for (const script of scriptList) {
    try {
      const module = await loadScript(script.url);
      loadedScripts[script.id] = module;
    } catch (e) {
      console.log(e);
    }
  }
}

function loadScript(url) {
  return System.import(url).then((m) => (m.default ? m.default : m));
}