Reproducing a D1 Dashboard using UIBUILDER?

I'm glad to hear you got through it! I've seen quite a few very nice dashboards built in uibuilder.

I went through the part of the same process and pains. Dashboard 2.0, as you said, doesn't include several of the elements I use in 1. I tried and struggled with uibuilder but just can't get past whatever mental block is stopping me from rebuilding my current dashboard in uibuilder so I'm indefinitely sticking with Dashboard 1. My dashboard is functional and works well.

Chris

As you have sensibly given yourself some breathing space, why not start a new thread where you share your current dashboard and let us see if we can't give you some steer towards redoing it in UIBUILDER?

We wouldn't need to hurry, just take it a step at a time. It would likely then also help other people facing the same journey.

Just a thought.

Maybe I'll do that. I'll think about it.

The main element of my dashboard is a table of pieces of equipment and their status populated by a MySQL query (with some processing in a function node just prior to the table). It seems like it should be simple but the documentation has never seemed straight-forward to me, although it probably is pretty clear. I know we've had this conversation a few times before and you attempted to help but I just wasn't seeing or understanding what I needed to. It's my own mental blocks preventing me from proceeding with it.

And this is absolutely what I want to understand. What gets in the way of people understanding how to use UIBUILDER simply. After over a decade of working on UIBUILDER, I'm often too close to things to be able see things that are obscure to beginners.

Let me move this to a new thread that we can use to take this further. When I've done that, if you can share an image of the Dashboard with the table and share some example processed output JSON from your query, that should give us something to focus on.

It might well be that I can do some improvements or share a web component that will make things easier and clearer.

I've got an eye problem at the moment and I'm off work so only able to do light computer stuff so this might be something to stave off some boredom! :smiley:

1 Like

OK. Yeah, let's do this. Sorry to hear about the eye problem! That's never fun.

I work in a theme park. I have NR running on a number of Pis and PCs at most of our rides, collecting data and updating a database on a server. The server is also running Node-RED and executes the MySQL query every second, although I could slow that down a little bit.

When rides are running, status information is displayed. When a fault occurs, the dashboard plays an audio file and uses TTS to read the ride name and error description so those in the shop who aren't close enough to the monitors to read the information can hear what ride went down and what the fault is.

I obfuscating the park and ride names intentionally.

[
{"Ride":"<b>1</b>","Mode":"<b>OFF</b>","Status":"<b>PLC RTC: 10/15/2025 15:13</b>"},
{"Ride":"<b>2</b>","Mode":"<b>OFF</b>","Status":"<b>Cycles this Year: 26595 (Today: 0)</b><b> / PLC RTC: 10/15/2025 15:13</b>"},
{"Ride":"<b>3</b>","Mode":"<b><font color='yellow'>OFFLINE</font></b>","Status":""},
{"Ride":"<b>4</b>","Mode":"<b><font color='yellow'>OFFLINE</font></b>","Status":""},
{"Ride":"<b>4</b>","Mode":"<b><font color='yellow'>OFFLINE</font></b>","Status":"<b>Cycles this Year: 16895 (Today: 264) (Train 1: 5686, Train 2: 8650, Train 3: 0)</b><b> / PLC RTC: <font color='yellow'>8/17/2025 23:23</font></b>"},
{"Ride":"<b>5</b>","Mode":"<b><font color='yellow'>OFFLINE</font></b>","Status":""},
{"Ride":"<b>6</b>","Mode":"<b>OFF</b>","Status":"<b>Cycles this Year: 17445</b><b> / PLC RTC: 10/15/2025 15:00</b>"},
{"Ride":"<b>7</b>","Mode":"<b><font color='yellow'>OFFLINE</font></b>","Status":"<b>Cycles this Year: 53717 (SECTION 1: 14837, SECTION 2: 14968, SECTION 3: 14337, SECTION 4: 0, SECTION 5: 4610, SECTION 6: 4965)</b>"},
{"Ride":"<b>8</b>","Mode":"<b>OFF</b>","Status":"<b>Cycles this Year: 28269 (Today: 15) (Train 1: 14158, Train 2: 14111)</b><b> / PLC RTC: 10/15/2023 15:09</b>"},
{"Ride":"<b>9</b>","Mode":"<b>OFF</b>","Status":"<b>Cycles this Year: 50543 (Cart 1: 0, Cart 2: 12858, Cart 3: 12870, Cart 4: 12866, Cart 5: 11949)</b><b> / PLC RTC: 10/15/2025 15:01</b>"},
{"Ride":"<b>10</b>","Mode":"<b></b>","Status":"<b><font color='yellow'> Alarm 0420 RV04 Watchdog Failure (High), Alarm 0520 RV05 Watchdog Failure (High), Alarm 0720 RV07 Watchdog Failure (High), Alarm 0001 ESTOP Went Active Alarm (High), Alarm 0013 OCC-100 Panel Enable Off (High), Alarm 0220 RV02 Watchdog Failure (High), Alarm 0320 RV03 Watchdog Failure (High)</font></b>"},
{"Ride":"<b>11</b>","Mode":"<b>OFF</b>","Status":"<b>Cycles this Year: 17323</b><b> / PLC RTC: 10/15/2025 15:08</b>"},
{"Ride":"<b>12</b>","Mode":"<b><font color='yellow'>OFFLINE</font></b>","Status":""},
{"Ride":"<b>13</b>","Mode":"<b>OFF</b>","Status":"<b><font color='red'> 035.09 ESTOP! Block B Slowdown Friction Brake 2 Press. Trans. detected pressure too low when brake off (engage). {I_BKB_FBR_02_PT}, 012.28 RideS! Block S Station Train Detect Sensor 7 Prox Switch (Exit) not flagged when expected. {BKS_TDT_7_PX_NC}, 014.06 RideS! Block B Slowdown Train Detect Sensor 8 Prox Switch (Exit) not flagged when expected. {BKB_TDT_8_PX_NC}, 014.26 RideS! Block S Station Train Detect Sensor 6 Prox Switch (Fwd Stop) not flagged when expected. {BKS_TDT_6_PX_NC}, 035.10 ESTOP! Block B Slowdown Friction Brake 3 Press. Trans. detected pressure too low when brake off (engage). {I_BKB_FBR_03_PT}, 004.03 ESTOP! Main Oper Cons (MOC) System Enable is off. {MOC1_SYS_SW}, 012.06 RideS! Block B Slowdown Train Detect Sensor 7 Prox Switch (Exit) not flagged when expected. {BKB_TDT_7_PX_NC}, 014.28 RideS! Block S Station Train Detect Sensor 8 Prox Switch (Exit) not flagged when expected. {BKS_TDT_8_PX_NC}, 020.30 ESTOP! Tow-Cart Retract Clutch Left Close/Op..."},
{"Ride":"<b>14</b>","Mode":"<b>OFF</b>","Status":"<b>PLC RTC: 10/15/2025 15:12</b>"},
{"Ride":"<b>15</b>","Mode":"<b><font color='yellow'>OFFLINE</font></b>","Status":"<b>Cycles this Year: 9388</b>"},
{"Ride":"<b>16</b>","Mode":"<b><font color='yellow'>OFFLINE</font></b>","Status":""},
{"Ride":"<b>17</b>","Mode":"<b><font color='yellow'>OFFLINE</font></b>","Status":""},
{"Ride":"<b>18</b>","Mode":"<b>OFF</b>","Status":"<b>Cycles this Year: 5375 (Today: 0)</b><b> / PLC RTC: 10/15/2025 15:10</b>"},
{"Ride":"<b>19</b>","Mode":"<b><font color='yellow'>OFFLINE</font></b>","Status":""},
{"Ride":"<b>20</b>","Mode":"<b>OFF</b>","Status":"<b>Cycles this Year: 22875 (Train 1: 12490, Train 2: 10385)</b><b> / PLC RTC: 10/15/2025 14:00</b>"},
{"Ride":"<b>21</b>","Mode":"<b><font color='yellow'>OFFLINE</font></b>","Status":""},
{"Ride":"<b>22</b>","Mode":"<b><font color='yellow'>OFFLINE</font></b>","Status":""},
{"Ride":"<b>23</b>","Mode":"<b><font color='yellow'>OFFLINE</font></b>","Status":""},
{"Ride":"<b>24</b>","Mode":"<b>OFF</b>","Status":"<b>Cycles this Year: 27018 (Train 1: 13409, Train 2: 13609)</b><b> / PLC RTC: 10/15/2025 15:11</b>"}
]
1 Like

Looks good. Cool use for Node-RED by the way. :smiley:

First question. I get that you probably want to write to a SQL table as a log. However, I'm guessing that you don't have so many rides that you can't keep the current data in memory? If that is the case, I would absolutely maintain the current live data in a global context variable.

Sure, write the copy to the DB but use the live data for the dashboard.

Personally, I would also simplify your data because a lot of what you have in the data table should actually be done by the front-end, all of the highlighting for example. Simpler is better. Trust the process - what I mean is that we will make the highlighting follow the data. So data something like:

[
    {
        "Ride": "1",
        "Mode": "OFF",
        "Status": "PLC",
        "RTC": "10/15/2025 15:13"
    },
    {
        "Ride": "2",
        "Mode": "OFF",
        "Status": "Cycles this Year: 26595 (Today: 0) / PLC",
        "RTC": "10/15/2025 15:13"
    },
    {
        "Ride": "3",
        "Mode": "OFFLINE",
        "Status": ""
    },
    {
        "Ride": "4",
        "Mode": "OFFLINE",
        "Status": ""
    },
    {
        "Ride": "4",
        "Mode": "OFFLINE",
        "Status": "Cycles this Year: 16895 (Today: 264) (Train 1: 5686, Train 2: 8650, Train 3: 0) / PLC",
        "RTC": "8/17/2025 23:23"
    },
    {
        "Ride": "5",
        "Mode": "OFFLINE",
        "Status": ""
    },
    {
        "Ride": "6",
        "Mode": "OFF",
        "Status": "Cycles this Year: 17445 / PLC RTC: 10/15/2025 15:00"
    },
    {
        "Ride": "7",
        "Mode": "OFFLINE",
        "Status": "Cycles this Year: 53717 (SECTION 1: 14837, SECTION 2: 14968, SECTION 3: 14337, SECTION 4: 0, SECTION 5: 4610, SECTION 6: 4965)"
    },
    {
        "Ride": "8",
        "Mode": "OFF",
        "Status": "Cycles this Year: 28269 (Today: 15) (Train 1: 14158, Train 2: 14111) / PLC RTC: 10/15/2023 15:09"
    },
    {
        "Ride": "9",
        "Mode": "OFF",
        "Status": "Cycles this Year: 50543 (Cart 1: 0, Cart 2: 12858, Cart 3: 12870, Cart 4: 12866, Cart 5: 11949) / PLC RTC: 10/15/2025 15:01"
    },
    {
        "Ride": "10",
        "Mode": "",
        "Status": " Alarm 0420 RV04 Watchdog Failure (High), Alarm 0520 RV05 Watchdog Failure (High), Alarm 0720 RV07 Watchdog Failure (High), Alarm 0001 ESTOP Went Active Alarm (High), Alarm 0013 OCC-100 Panel Enable Off (High), Alarm 0220 RV02 Watchdog Failure (High), Alarm 0320 RV03 Watchdog Failure (High)"
    },
    {
        "Ride": "11",
        "Mode": "OFF",
        "Status": "Cycles this Year: 17323 / PLC RTC: 10/15/2025 15:08"
    },
    {
        "Ride": "12",
        "Mode": "OFFLINE",
        "Status": ""
    },
    {
        "Ride": "13",
        "Mode": "OFF",
        "Status": "035.09 ESTOP! Block B Slowdown Friction Brake 2 Press. Trans. detected pressure too low when brake off (engage). {I_BKB_FBR_02_PT}, 012.28 RideS! Block S Station Train Detect Sensor 7 Prox Switch (Exit) not flagged when expected. {BKS_TDT_7_PX_NC}, 014.06 RideS! Block B Slowdown Train Detect Sensor 8 Prox Switch (Exit) not flagged when expected. {BKB_TDT_8_PX_NC}, 014.26 RideS! Block S Station Train Detect Sensor 6 Prox Switch (Fwd Stop) not flagged when expected. {BKS_TDT_6_PX_NC}, 035.10 ESTOP! Block B Slowdown Friction Brake 3 Press. Trans. detected pressure too low when brake off (engage). {I_BKB_FBR_03_PT}, 004.03 ESTOP! Main Oper Cons (MOC) System Enable is off. {MOC1_SYS_SW}, 012.06 RideS! Block B Slowdown Train Detect Sensor 7 Prox Switch (Exit) not flagged when expected. {BKB_TDT_7_PX_NC}, 014.28 RideS! Block S Station Train Detect Sensor 8 Prox Switch (Exit) not flagged when expected. {BKS_TDT_8_PX_NC}, 020.30 ESTOP! Tow-Cart Retract Clutch Left Close/Op..."
    },
   ...
]

Note that I've shortened the data as well as removed any formatting. The ride data could also be numeric rather than a string which would probably be more useful. I assume that you have a lookup table that converts the numbers into names?

Also note that I've split out the rtc to its own field - not sure whether you can do that but it is always easier to start with structured data, that gives you far more flexibility with the display. You might also consider making the rtc data yyyy-mm-dd hh:mm which is more easily sorted (and is actually more easily read by humans).

How many entries are we talking about? Is it just the 24? Because if so, this will be super easy. If it were >1000 or so, we'd probably have to do things differently.

One of the nice things about Node-RED is how easy it is to create a new, parallel flow. That means that you should be able to create and maintain the new global without having any impact on your existing flows and dashboard.

One final thing about the data, I note that you aren't really differentiating in the data between a machine that is simply off and one that is off because of an issue. It would actually be better to have additional modes. Maybe OFF-ERROR?

Anyway, this is the first stage - getting the most flexible and usable data. When we have that, it will be trivial to use a uibuilder node and a uib-element node configured to output a table from your global context variable. On each data update, you will need to (a) update the variable, (b) pass that updated variable to the uib-element node who's output is passed to the uibuilder node which updates the browser. We will worry about restarts and other finessing later on.

<existing flow that updates your SQL> -> ...
   +->New node to update the new global
          +->uib-element node (set to output a table)
                   +->uibuilder node

Hope that makes sense?

Late here, will catch up with you tomorrow.

Thank you, Julian!

I was getting ready to leave work, anyway, when I saw your response. My day is just starting although I'm not at work today but can still work on this after I get home from my son's doctor appointment.

I installed uibuilder here and added the uib-element and uibuilder nodes. The uib-element is set to Simple Table and the data is showing up! That's a lot farther than I was able to get before! Thank you! The thing I have to look at next is the fact that it adds new rows each time I execute the query. I don't have time at the moment to look at it but will in a couple hours. I'm not calling that a mental block just yet. I just need some time to look at it a little more but I have to get my son to the doctor.

I showed numbers in my JSON as opposed to ride names for the sake of not publishing the ride names or any park-identifying information here.

We have about 50 rides or so (don't know the exact number) so that's the only number of records I maintain, but many of them have a field in the database that specifies if a ride should be hidden from the display unless the ride has an active fault. In any case, maintaining a global context variable is absolutely viable.

What showed in my JSON was what we expect to see when the park is closed and we aren't cycling any rides, hence many of them being "OFF."

Some of the information I show in "Status" above won't be displayed when the ride is in operation, such as the PLC RTC. When a ride has an error, the only information I display is the error and how long the ride has been down for.

OK, that's at least a relatively simple Node-RED compute logic issue. Node-RED has lots of ways to update individual records in an array. Though I have to say that this is one of the reasons I often create an object rather than an array if it is likely that I will want to update specific records. It is much easier to be able to update myObj.record5 than having to filter the array each time. In your case though, as long as your incoming data updates include the machine number and that matches to the array position, you should be fine.

uib-element supports both input types by the way. :wink: So not too many worries there.

Well, that is trivial to handle - even on a Pi Zero! In fact, you could probably get away with using Node-RED's persistent context storage option rather than running a SQL DB (if you don't need the DB for other things). But that is by-the-by.

All good, I just wanted to point out that keeping data elements separate, even if they are only used for certain modes, is generally a much better option overall since it gives you ongoing options further into your processing, especially in the front-end display. But, in any case, we already have enough to work with.


The other thing I'll point out since you got the initial part working quickly. Since you only have 50 records, rebuilding the display table on any update is absolutely fine, especially when we move the formatting to the front end instead of sending it each time.

However, a unique feature of UIBUILDER is the ability to use the zero-code nodes (e.g. uib-element) simply to create an INITIAL layout which you then capture and expand on at your leisure. You can request the client to send you ALL of the current HTML which you can save to your index.html file. This is more efficient overall because mostly you only need to update a few elements in the UI when an update comes in.

In your case, I'm going to ignore that feature for now since it obviously adds a bit of initial complexity. Just bear it in mind should your UI start to get more complex or needs a bit of a performance boost later on.

BTW, for others reading this, I've produced live displays that include over 10,000 entries where the whole table is dynamically rebuilt on every data update. It takes a few seconds though so while it absolutely works fine, it isn't right for everyone and should generally be avoided. I just needed something quick and dirty and updates weren't happening often.

1 Like

I'm finally back at my PC. :slight_smile:

I guess the next thing for me to figure out is how to maintain just the one table on the screen but before I get too far and maybe I'm misunderstanding what you mean by updating individual records, but is it possible to update only a single row of the table if the data changes?

It absolutely is - of course :smiley:

If you examine the created HTML, you will see how each row and each cell is identified:

(this from the element example provided by uibuilder)

You can also use the dev tools in your browser to copy a unique CSS selector to identify a row or, as in this example, an individual cell: #eltest-tbl-table .r1.c2. You can use that selector with the uib-update no-code node. Please try out the provided examples:


Noting that one of the inputs updates a specific cell, another adds a new row to the table. You can, of course, replace a whole row or remove it.

Also note that the actual output of a no-code node is low-code - which is simply a specific set of JSON in a message. You can examine the message and you will find it easy enough to manipulate yourself. Also useful if you ever want to cut out a node and create the message yourself. Perhaps to do something that the current nodes don't currently do. This is one of the key differences between uibuilder's no-/low-code nodes and the Dashboards. UIBUILDER's nodes create configuration data that mirrors HTML. So as you get more confident and knowledgeable, you can continue to build up anything you want. One of the key design principles of UIBUILDER is that it ENHANCES native HTML, CSS and the browser API's, you do not reach a complexity cliff as you commonly get to with Frameworks.


But I don't want to confuse too soon :smiley:

For you, I would concentrate on managing the DATA. And, for each update, simply re-send the data to the uib-element node and let it do everything for you.

Honestly, it is more important to learn how to manipulate your data (a pure Node-RED task) because that learning gives you the biggest bang for your buck.

Later on, you can start to finesse things but lets get the basics right first.

2 Likes

I've definitely made some progress! Thanks again!

I have all my data showing and updating a single table. One of the things I need to figure out is how to set a dark theme, meaning I need to delve into css in uibuilder.

One question I have... Can I dynamically set data-row-name to the name of each ride instead of the row number? The reason I ask is because I have a number of rides I don't want to display unless a fault exists and my thought is that removing a row by name would be easier than determining what row number that ride is. I might be thinking about this the wrong way, too. :slight_smile: