🚀 [FlexDash] alpha release - a dashboard for Node-RED

I want to get more info about "Saving the config" for FlexDash.
There is a document in previous Demo site in the tab, websocket

Can you explain more about the part " Saving the dashboard config on the server end"
Is the server side config bound on node-red? Or I can implement the cofig server separately in different server?

1 Like

There is nothing in FlexDash that is specific to Node-RED. It can be used with other servers and I do that. However, I am currently focusing on the Node-RED integration and really can't spare the cycles to think about other use cases. You can see the code I use in another project sensorgnome-control/src/flexdash.js at flexdash · tve/sensorgnome-control · GitHub but I can't provide support at the moment, sorry. :cry:

1 Like

Thanks for Thorsten quick response.
One more question:
If I still need node-red editor to link uncertain nodes together in a little use case. Is it possible to run FlexDash seperatelly, but embed node-red editor in specific tab at the same time.
In my use case, 90% I can use FlexDash to connect private config server, but still have 10% to use node-red editor to link different node.

Is it possible to share share settings between private config server and node-red server?

It's not really possible. FD designates one connection as providing the config. The main issue is that it needs to know where to send config changes.

Yes, that would work well and for those fields the help text would be superfluous. I don't currently have the metadata for the options, but I can add that.

1 Like

No idea yet how that will behave, so can't compare it with the current way of working. What I like in our current way of working (with a seperate ui contextmenu node), is that you can build the contextmenu item list in your flow.

For example when you click on some device icon in your drawing, you can determine in your flow which actions that device in its current status offers. Which means you could execite an sql query, read your flow memory or whatever... It works entirely in the Node-RED way of doing things.

Not sure if that such dynamic menu items are possible with the contextmenu component?

Well, things would work differently... Vuetify has the notion of attached overlays. They're attached to another DOM element and you can specify whether it should be positioned above/below left/center/right aligned. Plus some offset. This then also takes care of corner cases, like you want it to the right but there's no enough space there.

The way I had thought of using the contextmenu component is that when you write a widget, you can use the contextmenu (or the vuetify overlay components too, if you prefer). In that case, what you feed into the context menu is up to you when writing the widget.

In addition, it would be easy to write a widget wrapper around the context menu. You could then pass it a DOM id and have it pop-up above/below that DOM element.

Now, how do you get DOM IDs? What I had thought is to add a _flexdash_widget_id prop to messages coming from the dashboard. That would have the ID of the widget which also happens to be its DOM ID (well, widget arrays are a bit different).

For your specific use-case, the SVG element, can an SVG element have a DOM id? If so, you need the SVG widget to transmit that and off you go.

It's a slightly different way of thinking, but I believe better than fiddling at the pixel level...

Yes an SVG element is a DOM element. You just need to specify a dedicated xml namespace when you create one. So that should work with your setup.
But where do you display the popup then: in the center of the clicked DOM element? I think such a fixed position (instead of at the click location) has some disadvantages:

  1. I am not entirely sure if it will look pretty. If I click somewhere on a large circle, and the popup appears at another location (e.g. in the center of that circle) that will look odd. But that is my personal opinion...
  2. Suppose a window (of your house) is visualized as a rectangle. When you click somewhere on that rectangle, you want your roller blades to go to that position (both in the svg drawing and in the real world physical device). In that case you would need to have the click coordinates. I know that some folks use it currently like that.
  3. If you use a canvas/video element to show IP cam images. If you click on the image somewhere you want your ptz cam to zoom in at that point (e.g. on a face). Then you need the location again in your flow.
  4. And so on...

So imho I think your dom id is very useful in the output msg, but coordinates are also a very welcome addition...

You can specify that with some attributes, like above/below left/right/center/overlap.

I'm not saying specific elements can't send the click coordinates but for most this doesn't seem to make much sense. If coordinates are needed for something general, it might be better to build a "get coords" function into the flexdash ctrl node that given a DOM ID returns x/y/w/h/...

DOM items can be variously identified by the ID or name. For Vue however, there is also the possibility of a REF.

uibuilder returns some standard data from its client-side helper functions.

For example - from the eventsend function docs:

eventSend function

Takes an suitable event object as an argument and returns a message to Node-RED containing the event details along with any data that was included in data-* attributes and any custom properties on the source element.

data-* attributes are all automatically added as a collection object to msg.payload.

The returned msg has an expanded set of data over the old client libraries eventSend function. All of the relevant data other than the payload is under the msg._ui property. The data comes from the "target" element which is the HTML element that was the target of the event. E.g. a button element.

msg._ui contains:

  • id: The element id if it exists.

  • name: The element's name attribute if it exists.

  • slotText: The text of the slot if there is any. Max 255 characters.

  • props: An object of property name/value pairs. Only contains custom properties which can be set when using this library to add/change elements or might be set when using web or other framework component tags. Element properties who's name starts with _ are excluded since these are assumed to be private.

  • attribs: List of attributes on the target element (without id, name, class attributes). Excludes id, name and class attributes.

  • classes: Array of class names applied to the target element when the event fired.

  • event: The type of event (e.g. "click").

  • Key modifiers: altKey, ctrlKey, shiftKey, metaKey. Any keyboard modifiers that were present when the event fired.

  • pointerType: The type of pointer that triggered the event (e.g. "mouse" or "touch")

  • nodeName: The name of the tag that triggered the event (e.g. "BUTTON"). Note that this may not be the same as the tag in your HTML - for example when using <b-button> from bootstrap-vue, the node is still called "BUTTON".

  • clientId: The uibuilder client ID. This is set in a session cookie for the client browser profile/URL combination for that browser's current session (e.g. changes if the browser closes and reopens but otherwise stays the same).

  • pageName: The url fragment of the page that contained the event (for multi-page apps).

Here is an actual example from uibuilder v5.0.2:

Now venturing into panels!

I have a spark line widget sized 1 x 1, and have created a panel sized 4 x 1.
The idea is to eventually add four 1 x 1 widgets into the panel.

But whenever I add the widget to the panel, it does not retain the 1 x 1 size, instead, it is stretched to 4 x 1!

What am I doing wrong?

lat

1 Like

Also... (sorry :wink:)

3 widgets in a grid (no panels), in order of 1) Chart, 2) Actual usage and 3) Grid.
I want them in a different order 1) Chart, 2) Grid and 3) Actual usage.

So I edit Actual usage, and 'move widget to bottom-right of grid', with the intention of moving it to the bottom and assuming position 3.

However, it does the opposite, and moves Actual usage to position 1 (top left) instead, which I assume is incorrect.

lat

1 Like

Yup. And I see that I need to fix the docs...
So what happens is that the vertical dimension of widgets in a panel is not fixed but more or less relative and stretched to fill the panel. I did this so one could add labels above/below other widgets and have them take up little space.
To achieve what I believe you want, make the plot 4-tall, then add 4 1-tall sparklines. This way the 4 rows end up minimum-sparkline height and then get stretched equally filling the whole vertical space. The plot ends up also filling since it's the same height as the 4 individual sparklines.

Can you please paste the flow? The buttons work the correct way for me so I wonder what's going on.

Yes pasted below.

[{"id":"423f5585248c5449","type":"function","z":"1543d308b342690a","name":"Chart config","func":"if (msg.hasOwnProperty(\"reset\")) {\n    context.set('Flexchart', [])\n    return\n}\n\nconst ts = (Date.now()) / 1000\nlet data = context.get('Flexchart') || []\nconst timestamp = Date.now() / 1000\n// msg[0] = grid, msg[1] = solar, msg4 = diverted\nconst newdata = [0, msg[0], msg[1], msg[2], (msg[4]/100)]\nnewdata.unshift(timestamp)\ndata.push(newdata)\nwhile (data.length > 150) data.shift()\ncontext.set('Flexchart', data)\nmsg.payload = data\nmsg.labels = [\"zero line\", \"Power grid\", \"Solar\", \"Diverted\", \"Diverter temp\"]\nmsg.widths = [1, 2, 2, 2, 2]\nmsg.colors = ['red', 'blue', 'green', 'grey', 'purple']\nmsg.axes = [\"left\", \"left\", \"left\", \"left\", \"right\"]\nmsg.left_unit = \" W\"\nmsg.left_decimals = 0\nmsg.right_unit = \" °C\"\nmsg.right_min = 10\nmsg.right_max = 40\nmsg.right_decimals = 1\nconst msg2 = { payload: newdata[2] }\nconst msg3 = { payload: (newdata[2] + newdata[3])}\nreturn [msg, msg2, msg3]","outputs":3,"noerr":0,"initialize":"","finalize":"","libs":[],"x":295,"y":245,"wires":[["43719d5359575f6b"],["79591432aa69fae8"],["5daed5829b4c1c75"]]},{"id":"5f4cdff849bed00c","type":"inject","z":"1543d308b342690a","name":"Clear Chart","props":[{"p":"reset","v":"","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":125,"y":215,"wires":[["423f5585248c5449"]]},{"id":"79591432aa69fae8","type":"function","z":"1543d308b342690a","name":"Spark line config","func":"var data = msg.payload\nif (data > 0) {\nmsg.color = \"red\"\nmsg.fill_color = \"red\"\nmsg.text_color = \"black\"\nmsg.unit = \"w\"\n} else {\n    msg.color = \"green\"\n    msg.fill_color = \"green\"\n    msg.text_color = \"black\"\n    msg.unit =\"W\"\n}\nif ((data >= 1000)||(data <= -1000)) {\n    msg.unit = \"kW\"\n    msg.payload = parseFloat((data/1000).toFixed(2))\n}\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":510,"y":245,"wires":[["8b94fa88b5f4c779"]]},{"id":"5daed5829b4c1c75","type":"function","z":"1543d308b342690a","name":"Spark line config","func":"var data = msg.payload\n    msg.color = \"red\"\n    msg.fill_color = \"red\"\n    msg.text_color = \"black\"\n    msg.unit =\"W\"\nif (data >= 1000) {\n    msg.unit = \"kW\"\n    msg.payload = parseFloat((data/1000).toFixed(2))\n}\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":510,"y":285,"wires":[["dc8bc52172ba458f"]]},{"id":"8b94fa88b5f4c779","type":"fd-spark-line","z":"1543d308b342690a","fd_container":"69c2e3f5798c3475","fd_cols":"1","fd_rows":1,"fd_array":false,"fd_array_max":10,"name":"Grid spark line","title":"Grid","popup_info":"","value":null,"color":"","fill_color":"","text_color":"","show_value":true,"unit":"","x":705,"y":245,"wires":[]},{"id":"dc8bc52172ba458f","type":"fd-spark-line","z":"1543d308b342690a","fd_container":"69c2e3f5798c3475","fd_cols":1,"fd_rows":1,"fd_array":false,"fd_array_max":10,"name":"Actual usage spark line","title":"Actual usage","popup_info":"","value":null,"color":"","fill_color":"","text_color":"","show_value":true,"unit":"","x":735,"y":285,"wires":[]},{"id":"43719d5359575f6b","type":"fd-time-plot","z":"1543d308b342690a","fd_container":"69c2e3f5798c3475","fd_cols":"7","fd_rows":"4","fd_array":false,"fd_array_max":10,"fd_output_topic":"","name":"Energy","title":"Energy data","popup_info":"","data":null,"labels":[],"colors":[],"axes":[],"widths":[],"span_gaps":[],"left_unit":"","right_unit":"","left_min":null,"left_max":null,"left_decimals":0,"right_min":null,"right_max":null,"right_decimals":0,"reverse_legend":false,"x":480,"y":210,"wires":[[]]},{"id":"70aab3442d949285","type":"inject","z":"1543d308b342690a","name":"","props":[{"p":"object","v":"{\"0\":-14,\"1\":293,\"2\":0,\"3\":23950,\"4\":2243,\"payload\":\"OK 10 242 255 37 1 0 0 142 93 195 8 (-53) \\r\\n\",\"port\":\"/dev/ttyAMA0\",\"_msgid\":\"d7d04626b3f893bf\",\"ack\":\"OK\",\"node\":10,\"rssi\":-53,\"time\":1661962663350,\"_event\":\"node:da5c88b65e756391\"}","vt":"json"}],"repeat":"2","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":125,"y":300,"wires":[["35a9707a5d30aa34"]]},{"id":"35a9707a5d30aa34","type":"function","z":"1543d308b342690a","name":"Example data","func":"msg = {\n    \"0\": -14,\n    \"1\": 293,\n    \"2\": 0,\n    \"3\": 23950,\n    \"4\": 2243,\n    \"payload\": \"OK 10 242 255 37 1 0 0 142 93 195 8 (-53) \\r\\n\",\n    \"port\": \"/dev/ttyAMA0\",\n    \"_msgid\": \"d7d04626b3f893bf\",\n    \"ack\": \"OK\",\n    \"node\": 10,\n    \"rssi\": -53,\n    \"time\": 1661962663350,\n    \"_event\": \"node:da5c88b65e756391\"\n}\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":120,"y":255,"wires":[["423f5585248c5449"]]},{"id":"69c2e3f5798c3475","type":"flexdash container","name":"Energy","kind":"StdGrid","fd_children":",92068bc37c49da87,43719d5359575f6b,dc8bc52172ba458f,8b94fa88b5f4c779","title":"","tab":"fef6c0f6d48841d6","min_cols":"8","max_cols":"11","parent":"","solid":false,"cols":"1","rows":"1"},{"id":"fef6c0f6d48841d6","type":"flexdash tab","name":"Demo","icon":"mdi-home-lightning-bolt-outline","title":"","fd_children":",69c2e3f5798c3475","fd":"e8f5aea52ab49500"}]
1 Like

I can't repro, i.e., up/down work fine for me. :angry: If you still observe the incorrect behavior, can you paste the "FD mutation" log lines that are printed in the node-red log when you click on the down button and it goes up instead? Something like:

FD mutation: fd=e8f5aea52ab49500 grids g69c2e3f5798c3475 {"id":"g69c2e3f5798c3475","kind":"StdGrid","title":"","min_cols":8,"max_cols":11,"widgets":["w43719d5359575f6b","w8b94fa88b5f4c779","wdc8bc52172ba458f"]}
FD mutation: 69c2e3f5798c3475 <- {"id":"g69c2e3f5798c3475","kind":"StdGrid","title":"","min_cols":8,"max_cols":11,"fd_children":",43719d5359575f6b,8b94fa88b5f4c779,dc8bc52172ba458f"}

Yes, still the same.

FD mutation: fd=e8f5aea52ab49500 grids g69c2e3f5798c3475 {"id":"g69c2e3f5798c3475","kind":"StdGrid","title":"","min_cols":8,"max_cols":11,"widgets":["x92068bc37c49da87","wdc8bc52172ba458f","w43719d5359575f6b","w8b94fa88b5f4c779"]}
FD mutation: 69c2e3f5798c3475 <- {"id":"g69c2e3f5798c3475","kind":"StdGrid","title":"","min_cols":8,"max_cols":11,"fd_children":",92068bc37c49da87,dc8bc52172ba458f,43719d5359575f6b,8b94fa88b5f4c779"}
1 Like

There is actually a difference. Your messages still have a missing/deleted widget, that's the first one in the children array (starting with 'x' on the first line):

FD mutation: ... "widgets":["x92068bc37c49da87","wdc8bc52172ba458f","w43719d5359575f6b","w8b94fa88b5f4c779"]}
FD mutation: ... "fd_children":",92068bc37c49da87,dc8bc52172ba458f,43719d5359575f6b,8b94fa88b5f4c779"}

I had the missing/deleted widget too when I imported what you pasted, but it didn't affect the behavior. I don't think I fixed anything in this area... I'll have to review the code and see whether there's anything I can make out. Thanks for your help!

Unfortunately in the flow editor it's basically impossible to tell whether a node (for which one has the ID) has been deleted or not (or at least I haven't figured out how to do that).

1 Like

I've now got 2 sparklines, 1 button & 1 text widget, all sized 1 x 1.
Added them to a 4 x 1 panel, and this is what I get -

cron

Maybe it's related to the "messages still have a missing/deleted widget" issue that you identified above?

1 Like

It really helps if you can post the snippet of the flow :wink:

As I'm building the flow I'm adding data sources and non-standard nodes.
I'm currently using Cron+ and watt2kwh, is that a problem?

1 Like