Alternative of current ui nodes in the FlexDash dashboard

Why does it matter what port the new dashboard is on? What matters is the path. The default for the current dashboard is /ui. Flex Dash might be /dash. It will no doubt be configurable as the current one is.

Why would anything have to be obsoleted because of the new dashboard?

3 Likes

Hi @PizzaProgram,
Not sure if you really need to second port number?

@tve has made his Flexdash very customizable. For example I can create multiple config nodes, one for each dashboard I would like to have:

image

So I get this:

image

And this:

image

So very easy to have multiple dashboards running in parallel, each with their own UI nodes...

2 Likes

And indeed like @Colin says, you can just run your old dashboard in parallel on the "/ui" path on the same Node-RED system:

image

2 Likes

And uibuilder :grinning:

Just make sure that all of your paths are unique.

1 Like

Because there was a discussion about maybe implementing a different Socket.io or something that is more responsive, etc.
That would have had caused trouble with the original dashboard.

But this is the past, now I see, @BartButenaers did an EXCELLENT job :+1: as he has showed on the prev. picture about :1990/... port.

I can not say how glad I am :slight_smile:
Thank you very much!

1 Like

It's perfectly possible to run multiple versions of socket.io concurrently on the same port. I do that every day with FlexDash... The only reason I'm aware of to use a port other than the standard one for the dashboard is security or other reverse-proxying type of configurations.

1 Like

It is to have different ExpressJS settings and middleware mostly.

Well, it took several restarts but finally I do have a Vite dev server integrated into Node-RED :tada: It all still feels a little wonky but I'll be using it in the coming days and will hopefully shake out some issues. Also, I'm pretty sure it doesn't yet work under Windows (unless you use docker) due to the \ path delimiter: I tried to use path.join everywhere but I know I missed a couple of spots (most notably glob always uses / delimiters), so some fixing will be needed.

If anyone want to give it a spin to report usability issues and errors, the installation is pretty simple:

  1. Download the repo of the nodes your will be developing. For the sake of this demo, do a git clone https://github.com/flexdash/node-red-fd-testnodes.git (you can also download just the sources)
  2. Install the node-red-fd-testnodes repo in a throw-away node-red install, e.g., do the npm install /home/me/src/node-red-fd-testnodes linking as you would with any other node repo you want to dev. Then start Node-RED.
    Alternatively, if you have docker launch a Node-RED container using the following fun command line (you have to adapt the "/home/me/src" part of the -v option, and you can change the port if you have another Node-RED running, e.g. -p 1990:1880):
    docker run --rm -ti -p 1880:1880 \
      -v /home/me/src/node-red-fd-testnodes:/usr/src/node-red/node-red-fd-testnodes --entrypoint \
      bash nodered/node-red:2.2.2 -c \
        "npm install ./node-red-fd-testnodes; npm start --cache /data/.npm -- -userDir /data"
  1. Open the Node-RED editor and in the palette grab a FD dev server node, drag it into a flow, and configure it:

    • create a FlexDash config, the defaults work fine for a throw-away test
    • enable the dev server in the FD Dev Server config (the checkbox is unchecked by default)
    • (I know that the "point your browser at" help text doesn't work yet)
  2. Deploy and watch the status under the dev server node, it'll show "npm install" for perhaps a minute while it runs npm install in the FlexDash sources (this downloads some 250MB). You can also watch progress in the node-red log

  3. Once the dev server shows a green running status, point your web browser at http://localhost:1880/flexdash-src (adjust the port as needed).

  4. You should see a "FlexDash loading..." page which will sit there for about a minute(!) while Vite compiles all of FlexDash, Vue, and Vuetify. This happens whenever the dev server is started.

  5. Eventually you will see FlexDash loading widgets and a "Welcome to FlexDash -- This is an empty dashboard..." widget. This is your FlexDash test instance!

Now let's do some development work to see hot-module-reload in action:

  1. Let's work on the TestButton widget. First drag a FD testbutton node into your flow and deploy just the changed nodes or changed flow. (If you deploy everything then Vite restarts and you'll have to wait a minute again...)
  2. You will immediately see the button appear in the dashboard. Its color will be dark red, unless you changed it when you configured the node.
  3. Now open widgets/test-button.vue in the node-red-fd-testnodes directory that you downloaded in step 1. This is the source code for the button widget. An easy change is to replace color: this.color, on line 34 by color: '#00ff00',, this causes the color setting to be ignored and always render the button in green.
  4. Save the test-button.vue file in your editor, you should instantaneously see the color of the button in the dashboard change to green. That's hot-module-reload for you...

For extra bonus, you can try out the debugging/inspection using the Vue browser plug-in:

  1. You will need to install the Vue dev plugin (chrome version, firefox version) and probably restart the browser(?).

  2. If you again navigate to the dashboard, you can open the dev tools (ctrl-shift-I) and should have a "Vue" devtools tab. Activate that and click on the "target" icon in the upper-right-hand corner of the devtools, then select your button widget in the dashboard.

  3. You will see the hierarchy of components on the page and if you click on the <TestButton> instance one up from the highlighted <VBtn> you will see something like:

  4. Notice how the bindings color show the #00ff00. This Vue inspector is the most useful feature of the dev plugin because it allows you to see how values propagate and what your widget does with them.

My plan is to write a widget -> node generator that produces a (functional) scaffold for a node based on a widget. This should make it easier for me to produce nodes for all the existing widgets... The main feature will be to dynamically generate a config tab for the widget's properties that can be included in the node's HTML. That'll keep me busy for a bit...

Step one of auto-generating the NR editor edit panel is to parse the widget source code. Thanks to some horrible RegExps and hacky use of new Function() I have a proof of concept! :nerd_face:

Step two is to prototype a tabbed editor layout so FD node authors can separate configuration of the NR node and configuration of the widget. That's looking pretty good so far.
Here's the node config tab:

And here's the widget props tab:

Next step will be to auto-generate the widget tab content. I'm thinking that the FD config node can do the following:

  • set-up filesystem watches on vue files in FD node modules
  • parse changed vue files
  • produce NR editor html in the module's resources subdir
  • load the HTML dynamically from the resources inside the widget tab

This leaves it pretty much to the user to decide whether to include the auto-generated tab or not. So there's full flexibility and convenience. :boom:

Hi @tve,
I did not have the chance to test anything, but I have some questions.

Could you explain in short what this Vite server does? I am not familiar with this kind of technologies...
And above in this discussion your wrote "I see two options to address this...". Which of both solutions did you choose? I assume the first one...
And am I correct that only developers need to have the Vite dev server node in their flow?

Personally I am not a big fan of those tabsheets. Will try to explain why:

  1. I truly can understand that you - as Vue frontend developer - see a clear separation of Node-RED config and the Vue widget config. However when I saw this morning your screenshot, I thought immediately: what are widget properties? When I see a UI node in my flow, then that node represents my widget. So all the properties in the config screen are there to configure that widget. So for me personally I liked your config screen without tabsheets more. But others may think different about that!!

  2. Since the config screens of all UI nodes should have a similar look and feel, I would have to add those tabsheets also on the config screen of my own UI nodes. But when I look e.g. at the SVG-UI node, it currently already has a lot of tabsheets:

    image

    Personally I would find it very confusing when I have to start adding all those tabsheets below the "Widget properties" tabsheet. Moreover how do I determine which config is Node-RED config or Widget config. Because some properties in my config screen will have impact on the server-side of my UI node. Is that Node-RED config then, or should I position it all under the "Widget" tabsheet?

Would be nice if some other folks could share their opinion about the tabsheets...

That is a clever idea, so you will be able to offer a series of widgets out of the box.
Since you setup file-system watches, I assume that the parsing happens at run time?
And that it does not work like this: there is a new Vue version supported by Flexdash, so you run a generator once to recreate your ui nodes...

My zero-Vue knowledge makes it hard to understand this. In the original discussion there was an idea from @dceejay that in a new dashboard every ui node would be a config node, but that a basic set of ui nodes (button, gauge, ...) would be installed out of the box. Do I understand correctly that this won't be the case now? That you still have two kind of nodes:

  • A basic set of standard ui nodes whose config screen will be generated automatically (at runtime)?
  • The contrib ui nodes that we can develop.

The issue that I see here is that ui node developers won't be able to look at the code standard nodes, to see how it is implemented there. Because it works entirely different. I mean: I cannot use the auto generation in my ui nodes, because I have lot of other stuff on my config screen...

That is not clear to me what you mean by that. Could you explain it a bit more please?

Hopefully you don't my feedback as negative, I just need to digest all the web development stuff a bit on my very-early-sunday-morning :wink:

Sure! When you write code for the browser the standard process is that the code you write isn't what is actually sent to the browser, rather, it gets transformed first. Kind'a like a program in a compiled language gets compiled and linked before you get to run it. For browsers the process generally includes:

  • convert typescript (or other languages and syntactic sugar, such as Vue's single-file-components) to pure javascript/html/css
  • add polyfills (compatibility shims) for older browsers
  • figure out which code is actually referenced and needs to be shipped to the browser
  • group small files into larger ones to optimize load time
  • minify & compress

There are several "bundlers" that perform the above steps, webpack and rollup are two of the more popular ones. But this bundling is a problem when you're developing because it can easily take several minutes to complete and you don't want to wait that long for each edit-test cycle.

So what Vite does is act as a web server that does the minimum necessary transformations on-the-fly as the browser is requesting stuff. So if the browser requests a .js file vite may serve it up as-is, but if the browser requests a .vue file then vite quickly splits the html, javascript and css portions and serves up something that pulls all three pieces in an appropriate way. You could think of this as being similar to just-in-time compilation vs. up-front compilation.

In addition to the transformations vite also watches your source files so if you change a source file it automatically retransforms it and sends the updated version to the browser. There a small piece of code swaps out the old code for the new code as seamlessly as possible. This is hot-module-reload: your modified code is running immediately without reloading the page. Of course this has limitations 'cause the new code may not work with the old data structures, but it's awesome for many small changes, in particular when you make small CSS and layout tweaks.

Yes and yes.

Yup, these are all valid points. I don't know either, so I'm leaving all options open. :sunglasses: An argument in favor of having the 'widget props' tab is that it makes clear which settings can be overridden by a message's props field. But for the auto-generated widgets having a node config tab with just name and FD isn't very nice either.

Sure! I haven't pushed the updated code to github because too much is still in flux, so you can't see it yet. I also don't really know what the workflow should look like :thinking:, I only know what some of the pieces in it probably should do...

Abstractly speaking, the automatic code generation produces two classes of artifacts: the content and the boilerplate glue. The content ones contain the information gathered from the widget: param names, types, defaults, help text. This is the stuff you don't want to manually keep in sync and want re-generated automagically. Then there's the boilerplate glue you may want to change so you can put everything together to your liking and you don't want those changes to be clobbered by auto re-generation.

Let me take a specific example: the gauge widget. The code generator produces two files:

  • resources/gauge-props.html with the the content of the widget props fields
  • gauge.html with the std Node-RED editor boilerplate

You want the first file to regenerate each time you change the props definitions in the .vue file. But while you may want to use the second file as a starting point, you want to be able to customize it to your liking.

So, starting the example at the beginning... the gauge widget .vue file is the source of everything and contains:

props: {
    max: { type: Number, default: 100, tip: "maximum value" },
    color: { type: String, default: 'green', tip: "color of filled segment" },
    ...

The generated resource file (see Loading extra resources in the editor) resources/gauge-props.html contains:

<div class="form-row">
    <label for="node-input-max">Max</label>
    <input type="text" id="node-input-max" class="fd-typed-input" input-type="num"/>
    <input type="hidden" id="node-input-max-type" />
    <small class="fd-indent">Maximum value. Default: 100</small>
</div>
<div class="form-row">
    <label for="node-input-color">Color</label>
    <input type="text" id="node-input-color" class="fd-typed-input" input-type="str"/>
    <input type="hidden" id="node-input-color-type" />
    <small class="fd-indent">Color of filled segment. Default: "green"</small>
</div>

You may recognize that this is what you normally type manually for each node configuration parameter. Now comes the boilerplate, which is in gauge.html. First we need an html element into which we insert the above form-row stuff:

    <!-- tab for the widget params -->
    <div id="fd-tab-props" style="display:none"></div>

and then we need to insert it dynamically, which we do in the oneditprepare function. The insertion is a one-liner using the JQuery .load() method, although the generated html expects the use of typedInput fields so there's a magic fdInitTypedInput callback function to do that:

    oneditprepare() {
      const url = 'resources/@flexdash/node-red-fd-testnodes/gauge-props.html'
      $('#fd-tab-props').load(url, fdInitTypedInput)

So where does that leave you? The generated gauge.html file only contains boilerplate, you can take it as-is, or you can modify it, or you can completely replace it. In all cases, you decide whether and how you pull in the auto-generated gauge-props.html with the individual form-row divs. For example, you can:

  • disregard gauge-props.html and write your own form-row html
  • modify gauge.html to remove the tabs yet still include the gauge-props.html
  • pull in gauge-props.html and then hide some of the form-rows because you don't want the user to set those because your code in the node computes them
  • pull in gauge-props.html and then tweak the types allowed by some of the typedInput fields

I didn't address it specifically, but my plan is for the node-red-fd-core module with the "standard" widgets & nodes to be no different from any "contributed" module. There should be a separate node-red-fd-samples repo with sample widgets that are suited to hack up into your own.

Something I don't understand yet is how exactly Node-RED loads modules and thus what my options are to hook into that process and generate stuff on-the-fly. As a result I don't know yet when what should get generated. The thing I do know is that I'll get it wrong and we'll have to figure out how best to do it. :wink:

Phew, I hope this long post was helpful...

The palette is getting crowded:

One minor problem I'm having is that all these nodes are in node-red-fd-corewidgets and even though that npm package depends on node-red-flexdash and the latter indeed gets installed by npm, its nodes are not added to the palette. The result is that the core widgets are unusable because they need the config node in node-red-flexdash. Is there a way to tell the NR package manager to load the nodes defined in a dependent package?

1 Like

@tve,
Hopefully I have interpreted your problem correctly. My node-red-contrib-ui-web-push node depends on the node-red-web-push node from another author. Since my ui node requires the config node from that other node, I have added that node to my package.json file:

    "dependencies": {
        "node-red-contrib-web-push": "^0.0.3"
    },

And that works fine, as long as you don't uninstall that other node manually of course...

1 Like

Your node doesn't seem to be in the NR flows library, so you can't really tell what happens if you install it using the node-red package manager :thinking: I'm gonna pull it in and then test... Hmmm, can you provide a link to the npm module?

Due to circumstances I have never published it on npm ...
And now it is a bit too late for that, as a result of the huge progress you made with FlexDash.
Because when I release it now, people will start asking support. And I have no time for that at the moment...

Perhaps I can create a local package for it tomorrow evening. See here. Not sure if that is 100% waterproof for your test...

When the time comes, you may read first this article again, because it just changed in the last 6 weeks:
https://nodered.org/docs/creating-nodes/packaging

And here you can Add a node to the palette:
https://flows.nodered.org/add/node

Hi @PizzaProgram,
Thanks for the links! But that is not the problem, because I have already published lots of other nodes. And the node already works fine, because I use it every day. But I am not going to publish it ever on npm, because I don't have time to give support (which will be certainly required for Android notifications issues).

1 Like

@tve,
I have been searching a bit on Discourse, and found some discussion that might give you some ideas:

  1. Here is the original announcement of my ui-web-push node, where you can see the discussion of how I solved my ui node's dependency of the web-push node. So the problem was here that the web-push nodes had a config node, and I needed the same config node.

  2. Here is somebody who had a similar question.

  3. Here is explained how I have shared code between my msg-speed and msg-size nodes.

Not sure if my approach from 1 is exactly what you need. Because my problem was that the config nodes where inside somebody else his git repo. But in your case you could put all kind of 'common' stuff into a separate npm package, and you could have all your repos (node-red-flexdash, node-red-fd-corewidgets, ...) depend on that npm package. Of course you need to make sure that your config node is only registered once, because otherwise Node-RED will start complaining (see 1).

Indeed it might again be better to start a separate discussion about this, to get some more response.

Quick update... I took a step back and wrote a tiny app to test the run-time linking of external widgets into FlexDash so I know how I can support pre-built Node-RED modules with UI widgets. During that process, the first Vuetify 3 beta got released so I'm now porting FlexDash to that (and Vue 3). So far so good, everything is lining up nicely, it's just going to take a few days to get back into a running state...

In the end, I believe the Node-RED integration will have the following features/options:

  • fd-custom node where widget source code can be typed into an editor (similar to ui_template)
  • NR node with flexdash subdir containing .vue files with simple widgets
  • NR node with flexdash subdir containing .vue files plus more stuff for more complex widgets that have dependencies on additional libraries
  • hot-module-reload and debugger for the last two cases, possibly also for the first case (fd-custom node)
  • auto-generation of NR config UI for widget params, can be overridden where desired
  • auto-generation of NR node code from boilerplate, can be edited at will

And then there's the whole config saving, which needs massaging to get to the point where one can export/import flows that contain FD widgets and keep the layout intact.

The thing that concerns me a little is that NR makes it quite cumbersome to provide a UI to manage all this. For example, it would be nice to take a fd-custom node and press a button to produce an NR node from it, i.e., auto-generate all the files needed. Similarly, it would be nice to be able to see error messages when something goes wrong and to be able to control the hot-module-reload. Maybe someday someone other than me can write a sidebar UI or something like that...

NB: I bumped into another interesting option while researching, which is to support plain Web Components. The current FlexDash widgets are Vue components with a few extra conventions, for example to provide help text and to declare an output. Vue components are Web components with extra capabilities and support, e.g. for reactivity. It would be pretty easy to wrap std Web components to pull them into FlexDash where the wrapper would provide the extra info/metadata. I don't know what that would really enable but it's a thought...

4 Likes

Just been playing with Svelte again and, having recently written a couple of nice web components and starting to experiment with configuration-led interfaces, I wanted a way to dynamically display both Svelte and web components. Turned out to be surprisingly easy. Svelte natively supports dynamic components in the UI. But to get native and custom web component tags dynamically displayed required just a small Svelte component "borrowed" from the Svelte site and tweaked to allow dynamic passing of attributes.

I spent quite some time trying to see if web components alone would be sufficient for creating new dashboards but the truth is - they aren't. They are far too low level. Great for simple things like my syntax-highlight component and the html-loader component. But once you start getting into dynamic interfaces and nested components - forget it, life is too short! I played briefly with web component build tools such as LegoJS but honestly, it wasn't worth it. By the time you've got to that, you may as well use Svelte or VueJS.

3 Likes