Frustrated with Error trying to use Business Text

  • What Grafana version and what operating system are you using? Linux vmubuntu01 6.17.0-41-generic

  • What are you trying to achieve? I’m trying to create a web styled interface, three panels, one a navbar, one a footer and a main panel.

  • How are you trying to achieve it? Display data in the main panel about data center devices in a rack layout

  • What happened? I have this working in a test system at work. I’m using 11.6 Grafana and Busines-Test visualization so I can use HTML, CSS and JScript. In work this is all working fine. However at home, I cannot get this to work. I have tried version 13. tried using different plugins, removed Grafana and re installed. I’ve tried pretty much everything to get this to work and it keep failing. I have the navbar and footer working but the main panel is now throwing an error. I can add my SQL to the code and run query to return data but as soon as it tries to render in the main panel, it throws error. It also references dynamictext in the error message, which is not selected. I’m thinking there is some quirk or maybe an olde file or even permissions to a folder that is not allowing me to use business text and defaults to dynamictest. I did have a problem accessing plugins at one point and had to change permissions. I wonder if perhaps Grafana cannot access certain key folders. The error is as below "ReferenceError: data is not defined

    at Fl (http://192.168.1.14:3000/public/plugins/marcusolsson-dynamictext-panel/module.js?_cache=6.2.0:363:2834)
    at jl (http://192.168.1.14:3000/public/plugins/marcusolsson-dynamictext-panel/module.js?_cache=6.2.0:421:1210)
    at div
    at div
    at http://192.168.1.14:3000/public/plugins/marcusolsson-dynamictext-panel/module.js?_cache=6.2.0:426:640
    at o (http://192.168.1.14:3000/public/build/8501.a435c109505704b50aad.js:1:513)
    at y (http://192.168.1.14:3000/public/build/8501.a435c109505704b50aad.js:12:151786)
    at s (http://192.168.1.14:3000/public/build/8501.a435c109505704b50aad.js:12:152332)
    at div
    at section
    at L (http://192.168.1.14:3000/public/build/8501.a435c109505704b50aad.js:32:26870)
    at div
    at div
    at Op (http://192.168.1.14:3000/public/build/7107.acb14953ceccac835c4c.js:17:167406)
    at ee (http://192.168.1.14:3000/public/build/7107.acb14953ceccac835c4c.js:15:100700)"
    
  • What did you expect to happen? In test it works fine, no issues, I get a grid layout of tables representing racks in a datacenter with devices and hostnames displayed.

  • Can you copy/paste the configuration(s) that you are having problems with?

My Grafana folders are located at

jb704y@vmubuntu01:/$ sudo find . -name grafana

./usr/share/grafana

./usr/share/grafana/public/app/plugins/datasource/grafana

./usr/share/grafana/public/app/features/alerting/unified/components/settings/_mocks_/api/alertmanager/grafana

./usr/share/grafana/public/app/features/alerting/unified/insights/grafana

./usr/share/grafana/packaging/wrappers/grafana

./usr/share/grafana/docs/sources/setup-grafana/configure-security/configure-authentication/grafana

./usr/share/grafana/bin/grafana

./usr/share/doc/grafana

./usr/lib/python3/dist-packages/botocore/data/grafana

./usr/lib/python3/dist-packages/ansible_collections/grafana

./usr/lib/python3/dist-packages/ansible_collections/grafana/grafana

./usr/lib/python3/dist-packages/ansible_collections/grafana/grafana/roles/grafana

./usr/lib/python3/dist-packages/ansible_collections/community/grafana

./usr/lib/python3/dist-packages/ansible_collections/community/grafana/roles/grafana

./run/grafana

./etc/grafana

./var/log/grafana

./var/lib/grafana

  • Did you receive any errors in the Grafana UI or in related logs? If so, please tell us exactly what they were.

Log file entry is as follows…

jb704y@vmubuntu01:/$ sudo tail -f /var/log/grafana/grafana.log
logger=plugins.update.checker t=2026-09-01T19:19:32.257695868Z level=info msg=“Update check succeeded” duration=365.967689ms
logger=context userId=0 orgId=0 uname= t=2026-09-01T19:25:16.023723996Z level=info msg=“Request Completed” method=POST path=/api/ds/query status=401 remote_addr=192.168.1.99 time_ms=0 duration=769.499µs size=105 referer=“http://192.168.1.14:3000/d/bfwtuy87pvegwa/three-panel-layout?editPanel=2&from=now-6h&orgId=1&refresh=5m&timezone=browser&to=now” handler=/api/ds/query status_source=server errorReason=Unauthorized errorMessageID=session.token.rotate error=“token needs to be rotated”
logger=context userId=0 orgId=0 uname= t=2026-09-01T19:25:16.024020259Z level=info msg=“Request Completed” method=GET path=/api/annotations status=401 remote_addr=192.168.1.99 time_ms=0 duration=448.619µs size=105 referer=“http://192.168.1.14:3000/d/bfwtuy87pvegwa/three-panel-layout?editPanel=2&from=now-6h&orgId=1&refresh=5m&timezone=browser&to=now” handler=/api/annotations status_source=server errorReason=Unauthorized errorMessageID=session.token.rotate error=“token needs to be rotated”
logger=cleanup t=2026-09-01T19:29:35.721210869Z level=info msg=“Completed cleanup jobs” duration=2.500275ms
logger=plugins.update.checker t=2026-09-01T19:29:36.19013265Z level=info msg=“Update check succeeded” duration=376.901736ms
logger=cleanup t=2026-09-01T19:39:38.902196154Z level=info msg=“Completed cleanup jobs” duration=2.503398ms
logger=plugins.update.checker t=2026-09-01T19:39:39.353291059Z level=info msg=“Update check succeeded” duration=358.578966ms
logger=infra.usagestats t=2026-09-01T19:40:34.036926468Z level=info msg=“Usage stats are ready to report”
logger=context userId=0 orgId=0 uname= t=2026-09-01T19:45:16.289868586Z level=info msg=“Request Completed” method=POST path=/api/ds/query status=401 remote_addr=192.168.1.99 time_ms=1 duration=1.201542ms size=105 referer=“http://192.168.1.14:3000/d/bfwtuy87pvegwa/three-panel-layout?editPanel=2&from=now-6h&orgId=1&refresh=5m&timezone=browser&to=now” handler=/api/ds/query status_source=server errorReason=Unauthorized errorMessageID=session.token.rotate error=“token needs to be rotated”
logger=context userId=0 orgId=0 uname= t=2026-09-01T19:45:16.290269879Z level=info msg=“Request Completed” method=GET path=/api/annotations status=401 remote_addr=192.168.1.99 time_ms=0 duration=782.723µs size=105 referer=“http://192.168.1.14:3000/d/bfwtuy87pvegwa/three-panel-layout?editPanel=2&from=now-6h&orgId=1&refresh=5m&timezone=browser&to=now” handler=/api/annotations status_source=server errorReason=Unauthorized errorMessageID=session.token.rotate error=“token needs to be rotated”

  • Did you follow any online instructions? If so, what is the URL?

I’ve install the Business Text plugin from the plugins menu but I’m wondering if I need to install this independently so that it goes into the correct folder for 11.6. I’ve gone around in circles for several days, tried as much as I can to get it working. I think this will be a simple fix but I have yet to find it. Any help of advise is much appreciated

Kind Regards

James

what does your in your ini file look like in both the working and not working environment

disable_sanitize_html = true

Likely cause → your JavaScript may still be using the old data parameter. Business Text 5.0 changed the code API so data is accessed through context.data.

If your code contains a bare data reference, try →

const data = context.data;

Then you can continue using data in the rest of your code.

Also check the panel’s Render template setting (Every Row, All Rows, or All data), since this determines what context.data contains.

your log shows →

POST /api/ds/query status=401
error="token needs to be rotated"

Log out and back in, then check whether those 401 errors continue. If they do, resolve that authentication/session issue as well, because the datasource query itself is being rejected.

I would not change plugin-folder permissions or manually reinstall the plugin yet.

Finally, if the home system is running Grafana 13 with Business Text 6.2.0, check that combination first. Business Text 6.x is documented as supporting Grafana 11 and 12, not Grafana 13

Hi Thanks for getting back to me. Just sat down after getting back from work.

Both my grafan.ini files in work (working) and home(broken) are as follows

;disable_signout_menu = false

Thanks

Hi thanks for getting back to me. I am getting data back for a few seconds. But its just the JSON data as shown below.. It shows in refresh, then a few seconds later I get the error

the main js is as follows, if that helps.

// MAIN PANEL RACK LAYOUT RENDERER

const container = document.getElementById('rack-container');

// Ensure data exists

if (!context.data || !context.data.series || context.data.series.length === 0) {

container.innerHTML = "No data returned from MySQL.";

return;

}

// Extract fields

const fields = context.data.series[0].fields;

const ASSETID = fields[0].values;

const building = fields[1].values;

const room = fields[2].values;

const row = fields[3].values;

const rack_position = fields[4].values;

const ustart = fields[5].values;

const uend = fields[6].values;

const fqdn = fields[7].values;

const machinetype = fields[8].values;

const serial = fields[9].values;

const description = fields[10].values;

const ip = fields[11].values;

const owner = fields[12].values;

const custodian = fields[13].values;

const parent = fields[14].values;

const cpu_usage = fields[15].values;

const mem_usage = fields[16].values;

// Build rack key from actual columns

function getRackKey(i) {

return `${building[i]}-${room[i]}-${row[i]}-${rack_position[i]}`;

}

// Group devices by rack

const racks = {};

for (let i = 0; i < ASSETID.length; i++) {

const key = getRackKey(i);

if (!racks[key]) {

racks\[key\] = \[\];

}

racks[key].push({

hostname: fqdn\[i\].split('.')\[0\],

ustart: parseInt(ustart\[i\]),

uend: parseInt(uend\[i\]),

machinetype: machinetype\[i\],

description: description\[i\],

serial: serial\[i\],

ip: ip\[i\],

owner: owner\[i\],

custodian: custodian\[i\],

parent: parent\[i\],

cpu: cpu_usage\[i\],

mem: mem_usage\[i\]

});

}

// Build HTML for racks

let html = "";

Object.keys(racks).forEach(rackKey => {

const devices = racks[rackKey];

html += `

<div class="rack">

  <h3>Rack ${rackKey}</h3>

  <table>

`;

// 42U rack, top-down

for (let u = 42; u >= 1; u--) {

let cell = "";



devices.forEach(dev => {

  if (u >= dev.ustart && u <= dev.uend) {

    cell = \`

      <div class="device">

        <strong>${dev.hostname}</strong><br>

        <span>${dev.machinetype}</span><br>

        <span>${dev.description}</span><br>

        <span>CPU: ${dev.cpu}%</span><br>

        <span>MEM: ${dev.mem}%</span>

      </div>

    \`;

  }

});



html += \`

  <tr class="u-row">

    <td>U${u}</td>

    <td>${cell}</td>

  </tr>

\`;

}

html += `

  </table>

</div>

`;

});

container.innerHTML = html;

Render Mode is ‘everyRow’

Both systems are 11.6.16, both have business text 6.2.0 and referencing - “type”: “marcusolsson-dynamictext-panel”,

[panels]

disable_sanitize_html = true

This is the config you need to checkout.

Your main.js confirms it → this needs a render mode + data access change.

Your script loops over every asset at once
(for (let i = 0; i < ASSETID.length; i++))
to group devices into racks it needs the entire result set in a single execution.
That can’t work in Every Row mode, where Business Text calls your function once per row. And per the plugin source checked directly against VolkovLabs/business-text on GitHub, context.data.series doesn’t exist in any render mode → this isn’t a naming fix, the data-access approach needs to change.

1. Change Render template: Every RowAll Rows (not All data → that’s for multiple queries/frames; you only have one).

2. Rewrite the data extraction. In All Rows mode, context.data is an array of row objects, one per DB row, keyed by your SQL column names. Drop the fields[n].values indexing:

js

const container = document.getElementById('rack-container');

if (!context.data || context.data.length === 0) {
  container.innerHTML = "No data returned from MySQL.";
  return;
}

function getRackKey(row) {
  return `${row.building}-${row.room}-${row.row}-${row.rack_position}`;
}

const racks = {};
context.data.forEach(row => {
  const key = getRackKey(row);
  if (!racks[key]) racks[key] = [];
  racks[key].push({
    hostname: row.fqdn.split('.')[0],
    ustart: parseInt(row.ustart),
    uend: parseInt(row.uend),
    machinetype: row.machinetype,
    description: row.description,
    serial: row.serial,
    ip: row.ip,
    owner: row.owner,
    custodian: row.custodian,
    parent: row.parent,
    cpu: row.cpu_usage,
    mem: row.mem_usage
  });
});

// rest of your rack/HTML-building loop stays the same

Everything from // Build HTML for racks onward is unaffected → only the extraction block changes.

Still worth confirming separately → whether the session.token.rotate 401s stop after a re-login, and whether disable_sanitize_html = true is set under [panels].

Thanks for the code update. I have implemented, set disable_sanitize = true and added the code snipet. Still seeing error 0 An unexpected error happened

TypeError: Cannot set properties of null (setting 'innerHTML') at Fl (http://192.168.1.14:3000/public/plugins/marcusolsson-dynamictext-panel/module.js?\_cache=6.2.0:363:2834) at jl (http://192.168.1.14:3000/public/plugins/marcusolsson-dynamictext-panel/module.js?\_cache=6.2.0:421:1210)

I think I have some sort of mismatch and for some reason I cant work out what is out. I can go one way or another but each time it gets blocked. I’m not sure why work version works and this version which I believe to be the same doesn’t The only difference is that the work systems uses API to get the data, while the home one I built a sample DB table to get the data, so two different data sources. Need to head into work now, will try again later. Regards James

Just sorted it. I had my HTML statement in the wrong panel window, I had it in ‘content’ rather than the html panel.. Feel rather stupid.

Now I can make it look pretty.

Thanks for your help. I’m sure I will hit further problems, so please stay tuned.