Uibuilder: to subscribe or not to subscribe?

If I understand you correctly, I like the idea of sub-components. For my roller shutters (Creating a UI with multiple buttons - #4 by Simon01011) this would be great. I could design the widgests just once and re-use it every time I automate a new device.

For me - not very familiar with HTML/JS/CSS - could make my work much easier :smiley:

Devil is likely in the detail. How complex would sub-components get?

Personally, I would certainly like to see some kind of sub-component feature. At least a grouping feature such as Dashboard already has. That is very much in keeping with the hierarchical nature of HMTL.

Other than that, I think we would want to be cautious about getting too complex. After all, the point about moving to use standard HTML methods is to make it much easier to create new components.

There is also a real danger that sub-components would be framework specific - if that is the case, I don't see much point in trying to force that into Node-RED since it should be easier to composite components using front-end code.

But don't forget that, done correctly, users will still be able to add their own front-end code directly. :slight_smile:

But I agree that there needs to be a method for people to create code from the Editor - we just don't need to be sending it over websockets to the front-end, we can simply use the Editor to edit a file that is easily included in the front-end. That way, it still gets to benefit from all the standard features of the ExpressJS web server and any caches. It could even get to be included in a build step.

And of course, noting that uibuilder already has an editor and the ability to send JS and CSS code to the front-end. But sending code is turned off by default because it certainly isn't very safe.

I think you will find that would be a feature no matter how it was implemented. It is such a common requirement.

I've been doing some learning and experimenting... I have some dashboard experiments using different types of layouts:

  • the top row is a relatively fixed grid layout with the idea to be able to populate a row or multiple with small status panels
  • the large "Orchard" blocks and "Card N" cards use a coarser grid for bigger components using native CSS grid with the "dense" option so spaces get filled. That stuff has gotten very powerful!

What I'm liking is the ability to define custom components, for example, the edit button for an orchard valve results in a modal:

What I'm not liking is that for simple stuff, like the temperature panels at the top, I have to create nodes in NR to send data and coordinate that with component instantiations on the web page. So I really want an "array of temperature components" component that can receive an array of temperatures and then instantiates the right number of sub-components.

For the big stuff, like the orchard valve table the split (some stuff in node-red some in the html) is fine, but in truth I could do without crafting the overall page html! I think what I'm gravitating towards is:

  • the dashboard has tabs
  • each tab has a custom list of grids arranged vertically down the page
  • each grid has different placement policies (for example small fixed-size blocks, or a two-column layout, or a masonry grid, or...)
  • node-red sends messages to each of the grids to populate it with components, the exact layout is then automatic
  • data is sent from node-red to each component for display

What this does is that I get to control the layout at a coarse level by using different types of grids but then the individual components just get placed in there. The assumption is that each component is a larger unit, i.e., I'm not trying to place a button component next to the gauge component it belongs to, instead, I'd first write a component with the button and the gauge.

Not sure where this going to end up yet...

NB: I need a hot reload solution quickly!
NNB: I hacked hot reload with the commandline inotifywait -m -r -e moved_to -e close_write -q data/uibuilder | nc localhost 1881 on linux and having node-red listen to port 1881 and send a reload message to uibuilder :slight_smile: (inotifywait is in the inotify-tools package on ubuntu and other distros). This could be 100x more sophisticated but for now it works :slight_smile:

1 Like

The dynamic component creation isn't all that difficult, yay! Here's an update of the previous example, the first two temperatures are hard-coded components but they're followed by an array of components. That array receives a message with an array as payload and instantiates one sub-component per array element. (I also inserted an "array" component that shows the number of elements (8) being received for debugging purposes.)

The html for the top two rows of temperature components looks like this:

        <v-container fluid class="g-grid-small">
          <stat title="bedroom" unit="°F" ref="bedroom"></stat>
          <stat title="garden oak" unit="°F" ref="garden oak"></stat>
          <array ref="temp-array">
            <template v-slot:default="slotProps">
              <stat v-bind="slotProps"></stat>
            </template>
          </array>
        </v-container>
  • the v-container is the grid container
  • the first two stats are hard-coded and receive data messages based on the ref (there's no value property as the value is updated dynamically via messages)
  • the array receives messages with the title/unit/value for the other 8 temperature components
  • the template/stat thing inside the array is what's being repeated and the slotProps stuff has to do with passing properties into the stat component

A message to the array looks like this:

{"topic":"temp-array",
 "payload":[
    {"title":"bedroom","value":73.4,"unit":"°F"},
    {"title":"garden oak","value":66.3800006866455,"unit":"°F"},
    {"title":"gh-air","value":71.0999984741211,"unit":"°F"},
    ...],
}

Here the topic routes the message to the array component (it has ref="temp-array") and the payload has the properties that are pushed down into the components.

Next step might be doing the same thing with a gauge sub-component...

Update: creating an array of gauges was faster than getting the gauges formatted to something legible... The message sent to the new array is identical to the old one, just the inside of the <array> tag is different...

2 Likes

Fancy :slight_smile: I like it.
Will it fit also into small screens? If there is no need then of course no problems, but big cards like the "Orchard valves" .. hard to figure out layout with such amount of info fields.

In general everything reflows well on small screens, but thanks for bringing it up! The wide panels, like the orchard valves, are too wide for cell phones in portrait orientation, so one has to either rotate the phone or scroll those panels. Left screen shot is on a pixel 2 phone just opening the page and scrolling down a bit, right screen shot is after pushing the table left:

Seems fine to me for occasional use. We normally use tablets or computer screen for this interface, not the cell phone. (Although if it got nice and fast...).

Next frontier: time series charts...

1 Like

Well, sometimes you have to do it all wrong and come full circle :slight_smile:

I went back to almost what @TotallyInformation has: messages contain property values that are just pushed into the components. I had changed this to a message dispatch where components contain a handler method that is called with the message. I had the linkage using the same ref= property as @TotallyInformation's examples have.

The reason I went back is that in Vue updating values should happen through properties and through the general "reactivity". Sending messages around or calling handler methods just goes around this and thus ends up clashing. In @TotallyInformation's examples the clash comes from vue complaining that props are being updated directly. Going back to using the reactivity mechanism properly reduced my code size significantly, everything is now so simple.

So what I ended up with is:

  • the app main (index.js) contains a nr data, which holds all data that has come in from node-red (hence "nr").
  • when a message comes in, it must have a topic and a payload, the message is simply stored as nr[msg.topic] = msg.payload (technically I have to use Vue.set, but that's insignificant).
  • the page html instantiates components and for components that need to get data from node-red the appropriate portions of the nr data are bound to the component's props using the standard Vue v-bind

And that's it, couldn't be simpler!

simple example

Simple example: a stat component that shows a title, a value, and a units string.

  • the component has 3 properties: title, unit, value
  • the page html instantiates the component and binds the value for updating like this:
    <stat title="bedroom" unit="°F" v-bind:value="nr.bedroom_temperature"></stat>
  • the NR flow sends a message to the page to update the temperature like this:
    { topic: "bedroom_temperature", payload: 77 }
  • the result:
    image

array example

A more complex example has an array component that expects an array of title/unit/value objects and generates an array of stat components for all those values:

  • the array component has some properties, including array (in this example there's no other property, but there could be in the future)
  • the page html instantiates the array component and provides the template for the stat component as follows:
    <array v-bind="nr.temperature_array">
      <template v-slot:default="slotProps">
        <stat v-bind="slotProps"></stat>
      </template>
    </array>

the v-binds used here bind all properties of the component, as opposed to the v-bind:value used above that only bound the value property

  • the message sent from node-red looks as follows:
    { topic: "temperature_array",
      payload: {
        array: [
          { title: "bedroom", unit: "°F", value: "75" },
          { title: "living room", unit: "°F", value: "72" },
          ...,
    ]}}

In this case, the array property of the message payload updates the property of the same name in the array component.


I uploaded the whole thing I have to GitHub - tve/uibuilder-test: Node-Red Test Dashboard using uibuilder in case someone wants to take a peek...

2 Likes

Indeed it is simple. You can also pass ALL properties to a Vue component using a single variable with just the singe v-bind, it does make your HTML a LOT simpler.

This is the power of VueJS with uibuilder :grinning: - all the tools are there, it is just a case of finding the best way to use them.

Proof of concept:

I'm really loving uPlot! (It's what grafana is moving to, so it's not just me...)
There's still lots and lots of work ahead, but at least I now have a POC for all the most important components. I'll stop hijacking this thread with new dashboard components chatter so it can go back a bit to the original message routing topic.

1 Like

Back to the whole to-subscribe-or-not-to-subscribe stuff... There are some pretty deep single-page-app vs. multi-page-app and single-user vs. multi-user questions buried in here...

Uibuilder (by default) creates a 1-1 relationship between a page and a uibuilder node. This works for simple multi-page apps:

  • when the user brings up a page a bunch of widgets are shown
  • data that the page needs can be sent to the uibuilder node corresponding to the page
  • a little caching function "wrapped around" the uibuilder node can provide the initial data
  • when the user navigates to a different page that displays different stuff that page connects to a different uibuilder node and gets different data

So far so good. However, as far as I can tell the directory structure used by uibuilder doesn't really match the way multi-page apps are organized, at least it doesn't seem to match the way vue-cli organizes them. Maybe it works if one creates a whole separate vue-cli project per page but then it becomes cumbersome to share components and assets across the pages/projects.

An alternative is to use a single-page-app. That sounds great, except that then the entire SPA is connected to a single uibuilder node. What this means is that all data produced in node-red for the entire app gets sent to the same one uibuilder node. Thus, while the user is looking at one tab (or equiv) the browser receives data for all tabs, most of which is a useless waste of bandwidth and browser processing and possibly browser memory.

To make a SPA work, one could wrap some filtering around the uibuilder node and have the navigation in the SPA send "I'm on tab X" messages back to node-red where the filtering node keeps track of which data should be forwarded and which not. Since multiple users can use the SPA at the same time this really means that the filtering node needs to keep track of which connection is on which tab and turn all messages from "broadcast to all connections" into connection-targeted messages. Essentially all this is a coarse subscription model where each browser subscribes to the messages that are destined to one tab.

An alternative to make a SPA work might be to hack uibuilder a bit. Instead of using one websocket connection for the lifetime of the app one could use one per tab. So basically while the browser stays on one page (SPA) as the user switches from one tab (or equiv) to the next the websocket disconnects from one uibuilder node and reconnects to a different one. I don't know how difficult that is, but so far this seems to be the path of least resistance.

If I abstract all this a bit, I would say that uibuilder has a coarse subscription mechanism that operates at a page level by default. As browsers hop from one page to another they unsubscribe from the old page topic by disconnecting and subscribe to the next one by connecting again. Nodes send data to subscribers by having their outputs wired to a uibuilder node that represents a page topic.

I think this coarse subscription model has quite some merit in its simplicity and integration into node-red. But I also think it would be really cool to extend it just a tad. It would be nice to be able to decouple the subscription/messaging part of uibuilder from the part that serves up assets. This way it would be easy to have one serving-up part ("uib-server" node) where the directory paths, modules, and such are configured. And then as many separate messaging parts ("uib_topic" nodes) that essentially serve as coarse subscription topics. The application writer can then decide what the granularity of these topics is, e.g., per page, per tab, per logical module, per chart, etc.

In the meantime I'll look into what it takes to hack uibuilder so a SPA can switch subscriptions...

1 Like

Not quite correct but certainly having your html/js pages in the src/dist folder makes things easier. This is because your front-end code needs to know what Socket.IO channel to use. the uibuilderfe code attempts to work this out but once you leave the top-level folder it gets too complex and you have to tell the start function where things are.

I'd love for someone with a bigger, better brain than mine to come up with some better code. :grinning:

But uibuilder certainly supports multi-page solutions - to a degree anyway. The other issue is having >1 websocket channel to use. That is currently not included but again I'm open to ideas on how to improve that.

There are, of course, hundreds of different folder structures used by different libraries. I chose the one that seems to be used in the majority of cases. A src folder (used by default for direct delivery if no build step needed otherwise used as the input to the build) and a dist folder (used only if it contains an index.html file, used for build output).

Ability to adjust the required folders is on the backlog but there are always ways to change the folder layout for build steps. You can therefore change your build config to match the required folders as I've done in several examples including for Svelte.

If you have a build stage, and if you are using a code-editor or IDE for development (as you would be if producing a large system), you don't need to use the src folder at all. Just make sure that you have something to serve up in the dist folder.

You can, of course, also use the filing system features of your operating system to position folders wherever you want to and simply use links. Managing larger deployments is therefore quite straightforwards.

I think you may have somewhat misunderstood how SPA's work.

It is certainly not a problem to have all data going through a single connection for an SPA and the link nodes make this easy to manage in Node-RED. It does mean that you need to split incoming data in the front-end. But other than that, it is no more overhead than any other SPA approach.

You can reduce the memory overhead of your SPA by using VueJS features to unload tabs not currently visible but of course data is still loaded unless you delete it.

The alternative approach is a multi-page app. But in this case you would only be simulating tabs since each tab is on a different page. That actually carries even more overheads which is why SPA's became popular in the first place. After all, an SPA is no different than a local app in the sense that everything is managed within a single wrapper. The overheads for a multi-page app are significant since you have to load and render the page each time and you have to load the data each time.

In most cases therefore, SPA's are more efficient. But there are certainly some edge cases.

As you point out, unless you only ever access the app from a single browser tab, that doesn't easily work. Though you could certainly make it work if you also keep track of each connection which uibuilder lets you do.

No, that is a really bad idea. If someone is going to the trouble of doing that, why not contribute code to uibuilder instead so that everyone benefits?

Nooo!!! Bad idea. And it doesn't really help that much if you follow the logic through.

If anything, uibuilder would benefit from being able to make use of the "rooms" feature of Socket.IO - this is also on the backlog. Please feel free to contribute a PR.

However, I really don't believe that helps your supposed tabs issue. Especially since I don't think you should be restricting the incoming data anyway. If the data for a specific tab only updates once every hour lets say, your clients may well want that data when it is issued so that tab switches remain instantaneous.

I think that you may be trying to over optimise memory and network traffic without actually working out if there is an issue?

No, that is also a bad idea. The whole point of a websocket is that you maintain the comms channel. What do you really gain from this?

Just tag the incoming messages so that the msg data goes to the right app variable. In fact, if you really wanted to make an improvement to uibuilder, that is a potentially really quick win that would help beginners as well as people creating large apps. Add a feature that lets you map msg.topic (or any other tag) to a local variable. So that you don't need to write the logic in the onChange function.

Please don't. I very much doubt that PR would be acceptable.

There are several areas you can help here.

  1. Contribute some uibuilderfe code that lets you do a simple configuration map based on an incoming msg property as the tag and a local variable as the target. This will greatly simplify things for the vast majority of cases.

  2. Look with fresh eyes at the uibuilderfe logic used to identify the Socket.IO details for a page and see if it can be generalised such that it can be used for any page at any sub-path.

  3. Look at the uibuilder node and start to work out how to move the core server code to a configuration node. This would be phase 1 of allowing other nodes to be build that could utilise the same connections. This is not a simple task though since it needs things like the servicing of front-end libraries to remain common across all instances. It also needs to make sure that re-deployment correctly removes all of the ExpressJS paths before adding them back again.

  4. Contribute core uibuilder code to enable more flexible use of folders. Using folders outside the uibRoot, using folders other than src and dist, allowing more dynamic switch between serviing up src or dist folders.

Hmm, I'm sorry I triggered so much defensiveness. This wasn't a post about "uibuilder is bad", it was about thinking how to use uibuilder as-is or extending it.

Looks like I didn't make clear what he mismatch is that I see. It's not about which specific folders are used, e.g. src or dist. It's that when I have a multi-page app, all artifacts end up pretty much in one folder, typically dist. But with uibuilder, each node owns a completely separate directory tree. So in order to have each page in the app connect to its own uibuilder node there's a mismatch: the app files are going to live under one uibuilder node's tree, but then the actual app needs to connect to a slew of uibuilder nodes. And each of those nodes will have a pile of orphaned template files in the filesystem and each page of the app needs to craft the namespace/ioPath explicitly. It can be done, but not nicely (IMHO).

I beg to differ. Certainly, using a SPA and sending it all the data regardless of which portion of the app the user is interacting with can work and has its benefits. But AFAIK that's what the std dashboard does and it has broken for me years ago. When I bring up the std dashboard I get a blank page and have to reload in order to get the content. The troubleshooting I've done indicates that it's because sending all the initial data hits timeouts, 'cause it actually is a ton of data. (And I've looked at it: the chart data really does add up.) I have many charts on every tab of the app and I very definitely do not want to send all initial data on load. I don't event want to stream all new data 'cause it comes in every 20 or 60 sec for hundreds of metrics.

Maybe if I split things up and just separate out the charts it can work. But I actually want to go the other way and turn everything into a chart, sort-of. For example, where I have a gauge I'd like to send the data for the recent past so if I click on the gauge I get to see a little sparkline so you can tell what the trend is.

So yes, I definitely want to break up the flow of data based on which part of the app the user is looking at.

I'm not familiar with socket.io's rooms, it sounds like it may be a way for the client to switch between logical connections. That would certainly be preferred over disconnecting the websocket and building up a fresh one to a different uibuilder node.

I wouldn't submit a hack as a PR ;-). At this stage, I want to try things with the least amount of up-front effort so I can validate ideas. Hence the term hacking. The idea I'm trying to validate right now is:

  • Nodes and the ui communicate via what I'll call subscriptions to coarse-grained topics, where each topic is represented by what I'll call a "uib-topic" node in NR: nodes are wired to these uib-topic nodes and ui apps connect or join these topics when appropriate.
  • UI apps can join multiple uib-topics at a time and are free to join/leave topics as the needs of the app change. For example, it should be possible to organize things such that each tab in the UI corresponds to a uib-topic node and/or such that each charting data source corresponds to a uib-topic.
  • The serving up of the app and the handling of the connections should be separate from the uib-topic nodes (but is of course related to them). Ideally, the connection between the ui app and node-red is shared across uib-topics but this doesn't have to be the case, certainly not for prototyping purposes.

Something I find amusing is that if I had to distill node-red into one sentence it would be "visual programming using a coarse-grained data flow paradigm" and here I'm looking for client-server communication using a coarse-grained pub/sub paradigm :slight_smile:

Something I've been reminded of while setting up my Vue tooling is that the two typical ways of serving up a Vue app are either using the dev server built into the tooling or using the dist packaging which results in a bunch of static files. In neither of those models there's really much use for uibuilder nodes serving up anything... Again, I'm not trying to attack uibuilder, I'm just musing and wonder why put so much effort into the editor portion of uibuilder when it doesn't match the way ui development works? An alternative, for example, might be to have uibuilder be able to proxy through to the dev server so one doesn't run into CORS issues.

:wink:

I've put a LOT of personal time and effort into uibuilder over the years so you will forgive me if I am somewhat passionate about it! It concerns me when I see people headed off down what I'm pretty sure will be blind alley's at best and disadvantageous to many at worse.

That is just an artefact of your build process isn't it? There is nothing stopping you having, for example, many different VueJS components who's source files are anywhere. You can run a build on each separately which is typically what you would do and then have the results in 1 or more npm packages. Or you can have them all together and use the build config to decide how they are packaged for distribution.

uibuilder doesn't stop you from doing any of this. It merely uses a convenient root folder to keep things together for the majority use-case. Have a think about your end-to-end workflow because there are certainly good and not-so-good ways to approach it. Personally, I would keep those components you've created completely separate - that's what I did with the extra's, they are a collection of Vue components, each in their own folder, each can be built independent of the others, the collection lives in an npm package.

If you need different pages with different comms channels, you can use separate uibuilder nodes of course.

If, on the other hand, you want components that can be shared between pages with the same comms channel, put the components into a separate repository and simply install it using npm to the userDir where you can reference it. Alternatively, put the dist files into the uibuilder/common folder which makes it available as a static resource to all instances of uibuilder.

Sorry, I'm not understanding that. I don't see any issues at the filing system level, uibuilder provides a number of flexible solutions there.

No, no, uibuilder works VERY differently to dashboard for that very reason. And that is also the reason I stressed at the start that we must not fall into the trap of sending code from Node-RED to the front-end.

uibuilder only needs the data, not the code (which is served directly using ExpressJS's static middleware).

I have a dashboard that sends a crazy amount of data from my Wiser home heating system - it sends the full dataset every 60s. You can barely notice it in the browser and it certainly doesn't stress Node-RED (even on a heavily utilised Pi3) and the network barely registers it even over WiFi.

Truth is that I've just been too lazy to deconstruct the data to send only what is needed on each pass, because it doesn't really need optimising. Maybe if I were using a cheap Android tablet to display the dashboard - maybe. I wrote the wiser nodejs library to enable me to only send changes, in fact, I even created a flow that also did it, it just hasn't been worth it to rewrite the front-end code.

Yes, Dashboard IS enormous. It used to be a LOT worse until Dave optimised it. But it is still a behemoth. But uibuilder isn't like that at all.

So now we are getting to something different. Your case is certainly an edge-case and not typical of most. And uibuilder doesn't prevent you from optimising the data transmission. So you can easily improve performance if you actually need to - just using the existing features.

What we need to do is to look at incremental improvements like the ones I outlined in my previous post. They will help most people not just a single edge-case. But they will also help your case too.

Have you tried this with Grafana? Because what you are trying to do is more suited to Grafana with all of the many years and hundreds of contributors that it has. If I had those resources to focus on uibuilder then things would certainly be different. :grinning:

But even Grafana takes an appreciable amount of time to switch between pages. If you are happy with that, then I think that your focus should be on working out how we can create multiple comms channels for uibuilder. Socket.IO has the capability, we just need to work out how to leverage it.

What I really meant was that forcing Socket.IO to disconnect and reconnect isn't going to help.

This is not unreasonable and might even be possible without having to split the core into a separate configuration node - e.g. staying with a single node but adding a routing configuration where the incoming msg tag routes the message to a specific Socket.IO room. And therefore just to those front-end pages that are configured for that room. If rooms don't cut it, multiple Socket.IO namespaces will.

This is where I really do regret not putting in extra work up-front to keep the uibuilder core in a configuration node. Unfortunately, when I started the journey, I didn't really have the experience and didn't want to delay the early delivery long enough to get it. The core of uibuilder is, as you've seen, somewhat complex.

However, this is almost certainly what is required to really get the best of what you are suggesting here.

I agree actually, the editor is there to help people without a code development toolchain. Its focus is on allowing people with limited experience to write simple data-driven web UI's. Please don't forget that this is the largest use-case. Accommodating large-scale web development has many different toolchains and workflows that are already very successful.

Lets not forget the core of Node-RED users while we are thinking about all of this.

I'm still open to further suggestions as long as we don't forget uibuilder's core tenets:

  1. Be an easy way to create data-driven web UI's
  2. Be open to people with limited experience in programming (a bridge if you will, just as Node-RED is a bridge to more complex programming)
  3. Be flexible to accommodate the largest possible range of use-cases and front-end libraries.

Forcing people down a process of build-steps, especially when that will have to accommodate a lot more than just VueJS, is not viable.

Really? When I run vue-cli-service build it takes all the stuff from wherever it comes and produces one dist folder. Whether I originally author my components elsewhere or not, by the time I build dist they all go together. There are probably ways to configure vue-cli to do it differently, but the out-of-the-box std is pretty clearly one dist dir.

What I'm trying to do is to use the most conventional tooling for developing Vue apps, and that's pretty clearly using vue-cli. During dev, you have vue-cli serve up the stuff and for prod you run a build and that produces a directory tree you plop down on your web server.

True, I could throw it all into the common dir, I hadn't considered that, but by then I might as well throw it into the static file area.

When I looked, it wasn't the code being sent, it was the amount of data. I can't reproduce it now 'cause I got too much grafana embedded into the dashboard so all I see is all the grafana stuff loading... But it's a good point, I can start with one uibuilder node for the whole app and see where it leads.

Yup, I have. All my data is in postgres and I've discovered that the SQL support in Grafana is not up to my standards and they're not putting any work into it. Also, embedding grafana is a pig. I moved all my graphs out of the dashboard chart stuff into grafana and now when I pull up a dashboard page I'm greeted with the "grafana loading" animation everywhere. (It's not all that bad on desktop, but awful on mobile.)

I'm also somewhat disappointed by grafana in that it's great at some things, but I deal a lot with climate data and there comparisons like YoY for the past N years can be really useful, but trying to make that happen in grafana is like pulling teeth...

Are you willing to reconsider this at all? I started with the build-less stuff 'cause I didn't want to have to think about installing vue-cli. Now that I've installed vue-cli I look back at the hours I've spent banging my head against the wall trying to load components that have dependencies and regret not having started with vue-cli.

That isn't the only build process. In fact the author of Vue has written a simpler, faster alternative to webpack (that a lot of people use with Vue) called Snowpack.

And I've already commented that this isn't a standard approach. Components should be packaged separately to your core code generally. But there isn't a hard and fast rule. If you are doing a build though, you need to be mindful of package sizes because there is a trade-off between having a single large package vs multiple smaller ones.

But please remember that Vue is only one approach for uibuilder users. Not everyone uses Vue and the whole point of uibuilder is that you can use any or even NO front-end library.

Also consider that on the backlog is an enhancement for uibuilder to tie into a local package.json in the root for an instance that will discover if you have a script called "build" and will add a button in the Editor to let you run it. That is reasonably universal so will help everyone who needs a build.

I have started to look at how uibuilder could gain the same auto-reload function and it isn't that hard to do. The dev servers in other build tools are a convenience for developing - particularly for more complex builds but all they do is an auto-rebuild and trigger a browser reload. Not very difficult to re-create within uibuilder.

Well except that I'm not sure how the Socket.IO connection would handle that. But otherwise yes, again, that's kind of the point of uibuilder, to use standard methods and mechanisms. Not reinventing the wheel for the sake of it.

Grafana is really designed to work with timeseries databases. If you have lots of time-based data, using something like InfluxDB gives you a massive performance boost and makes doing time-based aggregations easy. Not quite so easy with standard SQL - possible just not as efficient.

Yes, it really isn't designed at all to be embedded and you get the worst of both worlds when doing so since you have to load two large, complex web apps.

The reason I mentioned Grafana was to get you to compare what you have been trying with native Grafana in terms of browser load and performance.

Consider handling build steps (in a standard way) and browser auto-reload - certainly. Consider making large changes to uibuilder that only support VueJS - probably not. But if there are other changes that will support general use of uibuilder - almost certainly. As I say, I'm really open to suggestions and contributions but we do need to keep it on-track as a general tool.

However, I see that a PR has landed that aims to fix the CORS issue and that is certainly something that I will probably accept - I haven't had a chance to review it yet I'm afraid. Busy week at work so I've been tired in the evenings. I will try to get to it before the end of the weekend.

Lets keep the discussion going, I think that some really interesting ideas are starting to form that will be generally applicable.

1 Like

yes - great discussion - great ideas.

1 Like

:slight_smile: Let me just mention that in a past life, a little under a decade ago, I architected and wrote big portions of a monitoring system that used a nosql database for time-series storage and that monitored over 10M metrics coming from ~50k servers, which resulted in app servers processing ~10k requests/sec resulting in around 200k writes/sec into a redis cluster...

Right now I'm really happy with postgres for my small-scale time-series database use :slight_smile: and I'm not alone, see timescaledb...

I've reorg'd my dashboard UI project (and fixed the CORS issue). It now uses a conventional web dev workflow: run a test server on your laptop and deploy static files to production. The latter going into the static file area of NR. It makes my dashboard just like millions of other web apps out there (and the same as what I've been used to since writing my first SPAs back in 2013). I couldn't be happier, I get:

  • immediate hot reload on editing changes
  • nice error display directly in the browser window, syntax errors point to the line of the error not just "there's an extra } in component X" like with the buildless stuff
  • eslint catching lots of little mistakes
  • standard import statement handling, normal integration with installed node modules
  • full handling of the latest javascript language spec
  • optimized distribution package to get the best performance

Another really huge benefit is that I can have NR serve up the production UI while I'm hacking away at changes on my laptop, which connects to the same uibuilder nodes and thus gets all the same data without disrupting what other people see.

You write "Not reinventing the wheel for the sake of it." but I can't help but think that you are doing a huge reinventing in uibuilder, for example when you write "I have started to look at how uibuilder could gain the same auto-reload function and it isn't that hard to do" :wink:

My suggestion would be to split uibuilder into several pieces:

  • socket.io messaging node
  • file editing
  • http serving

The first one is useful for everyone, the latter two are not used at all when doing conventional web UI development, i.e. when using something like vue-cli-service, create-react-app, or svelte on one's laptop.

The socket.io node would have no relationship with the filesystem and the only relationship with the http service would be for the websocket. That could even run on a different port, if desired. As far as I can tell, the code for the messaging node would be less than 1/4 of the current code...

1 Like

Well I'm glad you've found a comfortable workflow. I think that the time it took to get to it is somewhat telling. Could the average Node-RED user use that workflow? Maybe but many don't have and don't want to develop the skills which is fine because that's where Node-RED fits anyway. uibuilder is merely another tool to give more options.

What is interesting is the workflow you've settled on and what can be picked out of it to improve uibuilder and make it easier for more people.

I take your point. However, the difference I think is that uibuilder lets you choose the framework without forcing you to use any other toolset than Node-RED. Of course, you can if you want to and that is likely to be a better workflow as you've discovered. But not everyone has the time or inclination and if I can spend some spare time on writing a tool that glues together the low-code back-end of Node-RED with a low overhead front-end (e.g. keeping front-end coding as simple as possible as well), then I consider uibuilder a success.

The potential big win for building something like the dev auto-reload into uibuilder is that it is another part of the toolchain that people no longer need to worry or think about. No matter what front-end library you use. You may wish to use different libraries for different UI's. Using "native" toolchains would mean setting up build tools and processes and configuration for each, if uibuilder could already do the core tasks (using simple standards underneath), then that's a no brainer for some use cases.

In any case, I'm not really reinventing here, simply building a simple, flexible wrapper that lets you leverage standard tools in a standard way. Frankly, I don't have the skills to do anything else :slight_smile:

Anyone can choose a more complex toolchain (from a setup perspective) in exchange for the longer term efficiency gains you get from it. But not everyone will need or want to invest that up-front time and effort. Especially for smaller projects.

For big, complex projects, sure, that time is well worth the investment and you probably don't really need something like uibuilder since Node-RED of course already has the websocket nodes and the ability to serve up files. That is mostly what uibuilder is doing anyway, but just making it a bit simpler for people to get going.

Without a doubt uibuilder needs some refactoring. Like many developments, it evolved over time as thinking, requests and my skills developed.

I see your thinking behind the 3-way split. Personally, I really like having only a single node, it really simplifies your flows in my view. Having the other parts split into modules would be sensible though if I can work out the logic. I kind of knew that I would probably get to that point in any case. The level of internal complexity is getting to the point where the logic is hard to remember or follow.

Maybe uibuilder v4 will be rather more of a rewrite than I'd anticipated!

Still, it seems as though you can work with uibuilder as it is which is a successful outcome I think and proves the basic concept. I'll now have to decide whether it would be quicker to refactor now but delay other developments.

If anyone else has ideas, please do contribute.

To be perfectly blunt "the time it took to get to it" is primarily due to the "blind alley" uibuilder led me down with the build-less approach!

Any JS web UI framework tutorial that I've looked at has some simple stuff you can try out in the browser, basically to make tutorials really easy, and then to do real stuff you install a dev tool. I'm convinced, and it has been my experience, that following the tutorials of the frameworks to get set-up is the easiest way for someone new to get started. And in that process uibuilder gets in the way.

We can agree to disagree. Nothing in the way I use vue-cli is specific to vue. It's exactly the same for just about any other framework.

Using build-less is for advanced JS folks that like to deconstruct the packaging and figure out how to import 3rd party modules without build step. I prefer to follow the flow and just do npm install foo and import foo from ./components/foo... I wasted a ton of time trying to import uplot and a bug-fixed version of vue-svg-gauge with the build-less model. Just my 2c...