FlexDash: towards a new dashboard for Node-Red

You are making nice progress.

I wanted to use uPlot for another standalone project but can not figure out how to show tooltip and color coded legend below graph at the same time as you are showing.

Can you please share code for uPlot setup?


Code is all on github. The uPlot wrapper widget/component is flexdash/time-plot-raw.vue at main · tve/flexdash · GitHub and the JS code for the tooltip comes from flexdash/uplot-tooltip.js at main · tve/flexdash · GitHub
It's the basic tooltip example from the uplot demos plus a day of fiddling :crazy_face:

Very interesting project indeed!
Maybe you have it but what about security for login? How is that handled?

I also have to chime in regarding multi-user and multi-tenant use cases. Those currently missing features is one major reason why Node-RED really can't be deployed in such installations. The mentioned freeboard alternative is maybe a possible way but it would be so much better if it would be "better integrated" with Node-RED itself. It's eay to wish, I know, and I have no clue if the current Node-RED has all the necessary properties on objects to allow for multi-user and multi-tenant (multi owners)

Thanks for the encouragement!

The current security for login is nil :laughing:
It's not something I've thought much about, I need to have more things in place before it really becomes meaningful.

WRT multi-tenancy, there's nothing a dashboard can do to make Node-Red somehow multi-tenant. If you want multiple users hacking their own flows your best bet IMHO is to spin up a NR instance per user. Containers are your friend here...

What a dashboard can do is provide different types of users access to what's cooking in NR. So a primary user can hack-up awesome flows and then provide access to different portions of the data produced by these flows to different dashboard users.

@tve Thank you for the links.

Looks like I'm doing "weekly progress updates" here... :partying_face:

This week I spent a lot of time on server connections:

  • it's now possible to save the dashboard configuration over a websocket connection, I have a flow for that but am too zapped to document and publish it, partly because:
  • there's now support for a socket.io server connection as well, and it supports saving the dashboard configuration as well
  • there's now an NPM package with FlexDash Node-RED nodes (node-red-contrib-flexdash - npm) that support the socket.io connection and implement all the shtuff needed to save the dashboard configuration to context storage
  • the demo is now updated with a socket.io tab that explains how to set everything up,
    including a demo flow to download
  • there's an iframe widget, an iframe grid, and an iframe tab! I'm not sure I like the iframe grid and may remove it, but the frame tab is pretty cool. There are two "iframe slots" and multiple tabs can share a slot which allows the std dashboard to be embedded very efficiently and seamlessly (see my previous post).

I believe with these improvements FlexDash has reached a state where I can start to migrate from the current dashboard to FlexDash! There's still a long to-do list and I'm sure I'll hit problems instantaneously as soon as I attempt to migrate, but there's also progress :horse_racing:

Server connections config panel:

Demo flow with socket.io-based FlexDash nodes:


Ha, so far 1:1 on the score of "it just works" vs. "oops, gotta fix something"

On the positive front, I have all my sensor data loaded in the dashboard :slight_smile: It was already all finding its way to a database node to be persisted so all I needed to do is to fork the data stream of into a couple of nodes to massage the format and push it into FlexDash :-).

This is a fragment of my "insert into the DB" flow with a link node on the left that receives sensor data from all the other flows, so I added a link-out node to forward the data to a flexdash flow:

The flexdash flow is really quite simple with an "annotate" node that uses some context tables to convert sensor IDs to names and such. And then everything goes into the topic tree with a path like devices/kitchen/temperature for the temperature and devices/kitchen/temperature_u for the unit (C) in this case.

On the "oops" front, my production Node-RED server is still on http (not https) and that makes it impossible to load flexdash from github and connect to Node-RED due to "mixed content" restrictions. (Any attempt to access http://tve.github.io/flexdash gets auto-upgraded to https.) I solved that by pushing flexdash to S3, so this works:


Of course the better solution is to upgrade my Node-RED install to https, which will happen soon :slight_smile:

Quick note that Paul found some bugs, which I fixed resulting in a new version on https://tve.github.io/flexdash and s3, the latter being http://s3.voneicken.com/flexdash/0.1.2/index.html
I need to improve this release engineering stuff... :thinking: what was I thinking? was I thinking?

1 Like

Quick heads-up that I'm hitting more issues than I'd like (mostly having to do with the reactivity stuff) so I recommend to hold off trying FlexDash until I shake more of the issues out myself...

1 Like

Just had to share... I'm really loving uPlot! Here's a wind chart that uses uPlot's feature to customize the drawing of the points of a series to draw wind direction flags:

(Yes, the left axis labels are getting clipped, more work needed...)


Nothing really in dashboard though I think Dashboard does now support middleware which could be used to help.

However, uibuilder is building out a standardised security capability that will allow for multi-user use. Some parts are already there in v3 but it certainly isn't production ready yet with more work to be done.

Node-RED itself does have multi-user capability - to a degree - and supports PassportJS. Though my personal recommendation is to do such things outside of Node-RED using a reverse proxy. Keeping your security layer separate from your development layer.

Weekly status update :partying_face:

This is the old dashboard front page, which is really a Grafana iframe:

And this is the FlexDash version:

I'm not fully there yet and the white chart at the bottom is a left-over iframe, but at this point it's just a matter of putting in the hours to finish things off.

I spent quite some time this week getting the uPlot stuff to work well, which meant dealing with resize events, switching between tabs, sizing the values on the axes correctly, yadda yadda.

I also switched the sparkline from the stupid vuetify thing to uPlot, added a color picker with the material design colors for the widget editing, and a tree picker to link widget inputs to a topic.

Color picker:

Topic tree picker:

There are still little issues left and right but I'm hitting fewer show stoppers on a daily basis. Next is going to be to implement some custom/complex controls to see how those translate to widgets. Something like:


Nice work Thorsten :+1:

I note that you have a lot of watch functions in your code - are those really needed? I thought the idea of Vue was that you didn't need to manually watch things, it should process everything naturally itself?

1 Like

Sometimes you have to help vue :wink:

It deals with props and things you use in the template. And it deals with computed values, but they must have no side-effects. So for things that don't fit into those categories you have to do create a watcher explicitly.

In general I find that if I have a "pure" vue component the computed stuff is almost all I need. But if need to interface to plain JS stuff, like uPlot, then there are too many side-effects and other things happening and I need watchers. At the end of the day, everything translates to a watcher anyhow...

Something I don't know what to do about is documenting or describing how to fill charts with data from a database. What I have so far is an inject node that ticks every minute and kicks off a bunch of SQL queries to retrieve the data for the charts and send it to FlexDash. And there's a little feedback link when a new FlexDash instance connects to also send the data. It looks as follows:

On the left I have a little delay chain that launches a query every 100ms, the queries are in the function nodes "in the middle" and then the right half is "boilerplate" to feed the queries through postgres and massage the output so it can go into FlexDash. The upper chunk is for normal charts, the bottom chunk for sparklines.

A query function node looks as follows:

// function to truncate time in `column` to `seconds` intervals
function trunc_time(column, seconds) {
    return `floor(extract(epoch from ${column})/${seconds})*${seconds}`

const query = `
    ${trunc_time("ts", 60)} AS time,
    location AS serie,
    avg(value) AS value
FROM wcc.measurements
    ts >= NOW() - interval '12 hours' AND
    metric = 'humidity'

return { topic: "queries/humidity", payload: query };

Looking at the where clause, it fetches the last 12 hours from sensors that issue a humidity metric, and then outputs the data in 1-minute intervals. The "return" at the bottom has the topic for the topic tree, i.e. this is what the corresponding time-chart subscribes to. In the flow after the postgres node I have a pivot function that pivots the data so uPlot likes it and then off it goes to FlexDash.

Obviously here the time span is hard-coded to 12 hours, but it would be easy to have some time-span widget in FlexDash that sends a specific number of hours and then weave that into the query.

Problem with all this is that it's really specific to my database schema and to SQL, although the general idea can of course be implemented with any other time series database as well. What I'm wondering is what would be the best way to make this stuff accessible to more novice NR users?

Here comes attempt #1 at reproducing the heating controls panel... :rocket:
In the std dashboard it's a panel with a carefully arranged set of controls and looks as follows:

In the new dashboard I made a custom thermostat control and plopped three down, plus two ValueSequence widgets for day start/end. This is how it looks right now:

The nice part is that the template for the custom thermostat control is simple and just uses widgets:

  <div class="flex-grow-1 width100 d-flex flex-column justify-space-around align-center">
    <gauge v-bind="gaugeProps"></gauge>
    <value-sequence :range="set_range" :value="day_setpoint" :unit="unit"
                    label="— Day —" :label_above="true" style="font-size: 80%;">
    <value-sequence :range="set_range" :value="night_setpoint" :unit="unit"
                    label="— Night —" :label_above="true" style="font-size: 80%;">
    <div class="text-body-2 mb-1">{{active_label}}:
      <v-chip small>{{active?"ON":"OFF"}}</v-chip></div>

The bad part is that the script part of the thermostat widget has a ton of stuff that doesn't really do anything but needs to be there to feed values through: :face_with_raised_eyebrow:

export default {
  name: 'Thermostat',
  help: `Thermostat control with gauge and day/night setpoints.`,
  components: {Gauge, EditPlusMinus},

  props: {
    // for the gauge:
    unit: { type: String, default: "°F", tip: "unit shown after temperatures" },
    temperature: { default: null, dynamic: "$demo_random", tip: "temperature shown by gauge" },
    min: { type: Number, default: 50, tip: "minimum of gauge range" },
    max: { type: Number, default: 100, tip: "maximum of gauge range" },
    color: { type: String, default: "green", tip: "gauge color between low/high thresholds" },
    low_color: { type: String, default: "blue", tip: "gauge color below low threshold" },
    high_color: { type: String, default: "pink", tip: "gauge color above high threshold" },
    low_threshold: { type: Number, default: 62,
                     tip: "temperature for color change, null to disable" },
    high_threshold: { type: Number, default: 80,
                      tip: "temperature for color change, null to disable" },
    // for the setpoints
    day_setpoint: { type: Number, default: 70, tip: "target temperature during the day" },
    night_setpoint: { type: Number, default: 65, tip: "target temperature during the night" },
    setpoint_min: { type: Number, default: 60, tip: "minimum possible setpoint" },
    setpoint_max: { type: Number, default: 80, tip: "maximum possible setpoint" },
    // for the current state
    active: { type: Boolean, dynamic: "", tip: "appliance active boolean" },
    active_label: { type: String, default: "Heater", tip: "label next to active status" },

  output: { default: null, tip: 'outputs setpoint as `{"day"|"night": value}`' },

  computed: {
    // put together all the props to pass down to the gauge widget
    gaugeProps() { return {
      min: this.min,
      max: this.max,
      value: this.temperature,
      color: this.gaugeColor,
      low_color: this.low_color,
      high_color: this.high_color,
      low_threshold: this.low_threshold,
      high_threshold: this.high_threshold,
      arc: 120,
      title: "",
      unit: this.unit,

    // range to pass into ValueSequence widget
    set_range() { return [this.setpoint_min, "...", this.setpoint_max] },

My conclusion is that this is nice if one needs to build a compound widget that has some smarts on its own. Then it's great that one can plop down some existing widgets and focus on building some cool logic around them.
But this is a lot of noise just to lay out a bunch of widgets without extra logic. Thus FlexDash needs some notion of a panel after all... :crazy_face:


Wheeee :rocket: :tada: after some copy-paste-hack-hack I have a panel widget into which I can place other widgets! Now my latest heating controls look as follows:

When editing things get a bit busy: :laughing:

Looks like I'm approaching the "final frontier": dynamically loading custom widgets...


I really like this approach. As well as the fun you seem to have with its development @tve :laughing:

1 Like

I concur. It is like watching a show on netflix. One episode each day :slight_smile:
I can't wait for the last episode (when flexdash is released). Then the true fun will start.
Thanks @tve for doing this!