[uibuilder] The next step - 3rd-party comms with a uibuilder front-end

Hi all, with the ongoing discussion about the future of Node-RED dashboards, I've been doing yet more work on making uibuilder a better foundation to build on.

This proposal lays out a likely approach to enabling anyone's nodes to talk to a uibuilder-defined front-end with a minimal amount of code and effort. This is aimed at node authors primarily.

Please do comment freely, as I know you will :grinning:

This isn't a done deal, it is an initial proposal and it is pretty likely that any end result won't look quite like things are laid out here.


uibuilder v5 will use the Node.js package ti-common-event-handler to create a shareable event handler
that facilitates communications between 3rd-party Node-RED nodes and uibuilder node instances (and back again).

Each uibuilder instance will act as a hub for sending data to its connected web clients and receiving messages back and routing them back to the originating node.

Two pieces of data will be required to enable this:

  1. The uibuilder URL
  2. A component identifier

How it works - the component node

A component node will need to:

  • Use the same event handler package. The module in the package is defined as a singleton class instance so it doesn't matter which node require's the module. All require's get exactly the same instance.

  • Use a standard event naming pattern (defined below).

  • Define a component identifier that front-end code will use.

  • Send events to the defined event name with a standard Node-RED msg object.

  • Add pre-defined metadata to the msg so that uibuilder and front-end code know how to process it. See below for details.

  • Register a return event handler with a specific naming convention (defined below).
    The uibuilder instance will maintain an internal map of component-id's to return event names so that messages back from the front-end can be automatically routed back to the originating node.

  • Send an initial control msg on Node-RED startup that will allow uibuilder to create the return map.

  • Send a final control msg on node removal, before re-deployment and on Node-RED closedown so that all event handlers are unregistered and the map is updated.

Front-end web components

Note that the component node does not need to define any front-end components. You could just write suitable code manually for your front-end.
However, there will be a set of standards that will allow a component node to make resources available to uibuilder-based front-ends.

The important thing is that messages will be automatically routed both to and from the front-end.

It is likely that there will be a mechanism in the uibuilderfe.js front-end library that will enable it to auto-load defined resources. Details to be defined later.

How it works - the uibuilder node

When a uibuilder node receives the first event from a component node as defined below, it creates an entry in a mapping table.

The table maps the links between the component nodes (based on the node's Node-RED id) and the component-id.
This allows return messages from the front-end to be routed back to the correct originating node for any processing that node wishes to do.
For example, it may choose to pass the returned message to its output port. This is likely to be the most common scenario.

Event naming standards

Event names will use the following standard to allow component nodes to send data:

node-red-contrib-uibuilder/<uib_url>/<component_id>

To facilitate return messages, the following standard will be used:

node-red-contrib-uibuilder/<node_instance_id>/return

Where:

  • <uib_url> - is the uibuilder URL which is set in the uibuilder node and is the unique identifier for uibuilder instances.
  • <component_id> - is a string identifier that is unique to your front-end code. It can be arbitrary. Component nodes can define defaults if preferred.
  • <node_instance_id> - is the Node-RED node id of the instance of the component node.

Standard msg metadata

The message metadata will take two forms:

  1. Firstly, some uibuilder standard data. This will define the root property to use so that we keep the pre-defined properties to a minimum which avoids name-clashes and avoids limiting how you can use a msg object.

    There is already a standard that uibuilder uses and it is likely that this would continue to be used and extended as needed.

    A component identifier string would certainly be required. As described above, this only needs to be unique within your front-end code.

    It is likely that a "component type" property would also be used to differentiate between data schema's.

  2. Secondly, some component-specific data. This would be defined by the component node's author.

    This would sit under the uibuilder property so as to avoid polluting the namespace of the msg object.

    It is possible that a schema standard would be defined to help but the data would mostly be defined by the component author.

Note that uibuilder already pre-defines some metadata for some tasks including security.

The future

Obviously, this design note only covers communication.

The approach has the strong advantage of being open. Which is to say that it doesn't tie you down to a specific framework since it does not define how your front-end code will process the data.

However, it does lay a foundation that will let nodes be created that will allow authors to create nodes with front-end components and will enable such nodes to fully automate both the component code delivery and the communications between the node and the front-end (and back of course).

This means that this approach is the first step towards a low-/no-code, simple to use, node-based web ui builder.

And yet, despite that, it does not break the core design principal of uibuilder which is to be framework agnostic and unopinionated.

2 Likes

And as expected, the implementation didn't quite follow the script :slight_smile:

However, I've now pushed a new branch to GitHub called vNext if you would like to try things out. Still some rough edges but those are mainly due to the part-finished security changes (don't try those, they don't work properly yet in this branch).

Check out the CHANGELOG file which details the changes so far and also contains a very messy To Do list.

If you want to try the external send/receive, create a simple uibuilder flow then deploy. Then change to the full Vue/bootstrap template. Then add a simple flow with a uib-sender node. Change the settings to select the correct uibuilder URL and check the "Allow return" checkbox and deploy.

In your front-end index.js file, change the uibuilder.on function to add the lines:

if ( msg._uib && msg._uib.originator ) {
    uibuilder.send( {payload:"This is a returned message!"}, msg._uib.originator )
}

Now, every time to send a msg in Node-RED to the sender node, you will automatically get a reply message to the same node.

You can also do uibuilder.setOriginator('<originator_node_id>') (obviously replacing the id with an actual id of a uib-sender node instance or picking one up from a received msg as above. This sets ALL sent messages from your client to go back to the sender node.

I will add a better example and suitable template as soon as I get a chance.

Have a play if you have time and let me know what you think. I'm pretty pleased with it so far. It seems to work OK but there is still a fair bit to do in order to get it ready to handle automated component messaging.

Some of the things still to do:

  • Work out the best way to map from a uibuilder-compatible node (like uib-sender) to a specific on-page component instance.
  • Decide whether uibuilder control messages should also be routable back to sender nodes (currently all control messages go back to the uibuilder node).
  • Work out a standard method for a node to tell the front-end what component needs to be loaded.
  • Work out a standard method for the front-end library to lazy-load components which may (or may not) be defined in a node
  • Work out a standard way for a node to provide a folder of web resources (including maybe web or front-end framework components) to uibuilder front-ends. Not only adding the folder(s) to uibuilder's web server but also automating the load of specific resources.

Then there is still a bunch of work to do finishing off the editor rework, updating package management to allow scoped packages, GitHub packages and specific package versions to be installed along with indicators and controls for updates needed. Then there is the security stuff that I've left alone for a bit in order to try and get some more inspiration.

Anyway, install the vNext branch direct from GitHub if you want to have a go.

Without a doubt, when vNext is released, it will be uibuilder v5.

1 Like

Tihis sounds promising :+1:. Can somebody buy me some time to play with it ...

2 Likes

Another round of changes pushed to the GitHub vNext branch today.

  • A couple of annoying bugs got squashed.

  • Package Management is now almost completely rewritten - still some work to do to allow you to install local packages though. But installing from npmjs and from GitHub both work and you can add tag/version/branch specifiers as well. Useful if you want a specific version or branch.

    As mentioned, uibuilder packages are no longer merged into the Node-RED userDir folder but instead are now in the uibRoot folder (defaults to ~/.node-red/uibuilder for most people). This means that you will need to remove the packages from your userDir and re-add them in the correct folder. Using uibuilder itself is the easiest way to add them back in but the changes also mean that you can install them yourself, when you restart node-red, uibuilder will work everything out as long as the package.json file in uibRoot was updated with the installed packages (the default for npm).

  • The Editor panel has been extensively rewritten as well.

    Some areas of the panel are inaccessible until you have deployed a new instance - which creates the server folders required. Should save confusion, especially if you haven't used uibuilder before.

    New error information is available when you are setting/changing the URL.

    The extra information about installed packages is now included on the Libraries tab. Including the installed version and an estimate of the correct URL to use in your front-end code. There is also a link to the packages homepage (if specified in its package.json file), you can click on the package name. If not known, an npmjs.com search is performed.

    And of course, everything in the panel is laid out much better than before.

  • Behind the scenes, the ExpressJS route handling has been extensively reworked and now uses Express's Router capabilities. This greatly simplifies how routes are created and removed. Especially when instances of uibuilder have their URL changed or when an instance is removed from the flows.

Still left to do is simplifying and tidying up the security feature - I fear it won't have all the features I'd hoped for in this release. Also I want to include @shrickus's idea to allow a custom middleware to be added to an instance so that API's can be added.

A couple of other bugs to squash too.

The changelog file in the branch has details of the changes and a long todo list (not all of which will make it into this release) :slight_smile:

Please do try it out if you have a chance and let me know of any bugs, issues or wishes.

1 Like

I'm trying to test out vNext but I can't seem to get NR to start successfully, not exactly sure what I'm doing wrong. I have the vNext branch installed with a github URL. But then upon starting NR, it crashes because it can't find execa (listed in uib's devDependencies, this should be regular dependencies, no?). So I installed execa@5 to <userDir> (using Docker for this so /data), then got a similar error with socket.io not being found. Did the same with that, now ending up with this and I'm not quite sure if it's a uibuilder error or node-red. I do see a mention of :3001 here, but still not sure.

node-red_1  | Welcome to Node-RED
node-red_1  | ===================
node-red_1  |
node-red_1  | 29 Dec 17:31:47 - [info] Node-RED version: v2.1.4
node-red_1  | 29 Dec 17:31:47 - [info] Node.js  version: v14.18.2
node-red_1  | 29 Dec 17:31:47 - [info] Linux 5.10.16.3-microsoft-standard-WSL2 x64 LE
node-red_1  | 29 Dec 17:31:47 - [info] Loading palette nodes
node-red_1  | 29 Dec 17:31:48 - [red] Uncaught Exception:
node-red_1  | 29 Dec 17:31:48 - [error] Error: connect ECONNREFUSED 127.0.0.1:3001
node-red_1  |     at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1159:16)

So clearly I'm doing something wrong here :sweat_smile:

Oops! First bug found - thanks! :grimacing: - because I run with dev dependencies installed, I forgot that I had used execa in one of the new classes as well. I use it to support the new package management functions. That is now fixed in vNext.

That shouldn't happen and you shouldn't install it.

I've just done a clean installation and got no errors at all from the latest amendment:

C:\src\nrtest\data
> npm install totallyinformation/node-red-contrib-uibuilder#vNext
C:\src\nrtest
> npm start

> node-red-master@2.0.3 start
> node node_modules/node-red/red.js --userDir ./data

29 Dec 22:35:39 - [info] 

Welcome to Node-RED
===================

29 Dec 22:35:39 - [info] Node-RED version: v2.1.4
29 Dec 22:35:39 - [info] Node.js  version: v16.13.0
29 Dec 22:35:39 - [info] Windows_NT 10.0.19044 x64 LE
29 Dec 22:35:40 - [info] Loading palette nodes
29 Dec 22:35:44 - [info] Settings file  : C:\src\nrtest\data\settings.js
29 Dec 22:35:44 - [info] Context store  : 'default' [module=memory]
29 Dec 22:35:44 - [info] User directory : C:\src\nrtest\data
29 Dec 22:35:44 - [warn] Projects disabled : editorTheme.projects.enabled=false
29 Dec 22:35:44 - [info] Flows file     : C:\src\nrtest\data\flows.json
29 Dec 22:35:44 - [info] Creating new flow file
29 Dec 22:35:44 - [warn] 

---------------------------------------------------------------------
Your flow credentials file is encrypted using a system-generated key.

If the system-generated key is lost for any reason, your credentials
file will not be recoverable, you will have to delete it and re-enter
your credentials.

You should set your own key using the 'credentialSecret' option in
your settings file. Node-RED will then re-encrypt your credentials
file using your chosen key the next time you deploy a change.
---------------------------------------------------------------------

29 Dec 22:35:44 - [info] +-----------------------------------------------------
29 Dec 22:35:44 - [info] | uibuilder v5.0.0-dev initialised
29 Dec 22:35:44 - [info] | root folder: C:\src\nrtest\data\uibuilder
29 Dec 22:35:44 - [info] | Using Node-RED's webserver at:
29 Dec 22:35:44 - [info] |   http://172.20.112.1:1881/ or http://localhost:1881/
29 Dec 22:35:44 - [info] | Installed packages:
29 Dec 22:35:44 - [info] +-----------------------------------------------------
29 Dec 22:35:44 - [info] Server now running at http://127.0.0.1:1881/
29 Dec 22:35:44 - [info] Starting flows
29 Dec 22:35:44 - [info] Started flows

Well, you are using WSL - I'll see if I can get that working. However, I'd say that you have something else trying to connect to port 3001, I don't think that would be anything to do with uibuilder since Node-RED hasn't even actually started at that point.

Hang on, you are using Docker - under WSL? Urm, that's about as complex as I can think of!

Well it seemed like it would be the simplest way to have "throwaway" Node-RED installs and the target device will be running this in Docker on a Debian machine. But fair point :grinning_face_with_smiling_eyes:

OK, I've reproduced the error. Obviously I was wrong. Seems to be something to do with WSL.

Will check now.

But I don't know why - that net.js isn't one of mine.

Oh great, glad I'm not the only one. Thanks for taking the time to look into it!

Tomorrow I can try this with/without Docker on an actual linux machine to see if I have better luck.

Certainly not Docker in this case. I don't get it on a local install on Windows as you saw. But do get it on WSL with a global Node-RED install and node.js v14. My Windows dev has v16. Node-RED itself started up OK without uibuiilder installed.

Oh poo! Not going to be easy to track down I don't think. I can't think what could be looking for something on port 3001 though. I'll do a manual code search. Let me also try the live version of uibuilder under WSL.


UPDATE: Doh! I'm a moron - I found something hard-coded that I'd obviously been working on for the security stuff. It was a mockup of an API. Let me get rid of it.

OK, seems to be working now - sorry about that.

Not a problem at all! Thanks for saving us from the impending dashboard/Angular deprecation!

I'll give this a shot tomorrow and let you know if I have any other issues. Thanks for the quick fix.

1 Like

I feel bad bringing more bad news, hopefully I'm not getting too annoying yet!

I'm a bit confused on the dependency installation process. I tried to install my uibuilder dependencies from the Dockerfile (RUN npm install vue@2 bootstrap-vue --prefix /data/uibuilder) but it looks like uibuilder overwrites the package.json file upon initialization. So even though these packages were present in /data/uibuilder/node_modules, since package.json was now blank uibuilder warned about the missing packages when attempting to load the bootstrap-vue template.

It would be nice to automate this part and have it all handled in the Dockerfile, but you provide the "Libraries" tab so I figured I'd try it that way instead. But I'm not having any luck with that either:

[error] [uibuilder:API:uibnpmmanage] uibuilder Admin API. No location provided for npm management.

Is there something new I need to configure for the "npm management location"?

If I manually run npm install vue bootstrap-vue inside /data/uibuilder then restart Node-RED, they come up just fine.

image


I'm also still getting the socket.io not found error, but messages are successfully getting passed back and forth just fine so maybe it's not actually an issue?

30 Dec 19:47:48 - [warn] [uibuilder:package-mgt:getPackagePath2] PACKAGE NOT FOUND socket.io at /data/node_modules/socket.io
30 Dec 19:47:48 - [error] [uibuilder:web.js:serveVendorSocketIo] Cannot find installation of Socket.IO. It should be in userDir (/data) but is not. Check that uibuilder is installed correctly. Run 'npm ls socket.io'.

I still managed to get it running fine as far as I can tell so I'll continue to do some more testing.

Not at all, it needs doing. Bring it on.

Doh! Again! Right, I obviously hadn't thought through the different paths through that process.

OK, so it needs to check whether the file exists and if it does, not destroy it! OK, added to the fixes list.

Nope, also my fault. In a previous dev update, I changed the way things work. Previously you had to specify whether you wanted the package from npm, github or local but I realised that wasn't needed since that is inferred from the name. But clearly I missed a parameter check in the API. (Line 674 of admin-api-v2.js).

I've pushed a fix for that to the repo so that should work. Just make sure that your uibRoot folder is writable in your Docker setup. It should be of course.

That's strange. That is something different. Could you please, from a terminal, go to your userDir folder and run npm ls socket.io? You should get a result something like:

image

Let me know what yours shows please - most likely something weird due to a combination of Docker and npm I'm guessing.

Well this took me a while to type up... I'll get back to your recent messages in a bit.

The uib-sender node seems to work well (almost, see below), but I'm not sure I understand when it should be used. Does it provide different functionality than the input to the main uibuilder node (or planned to in the future)? What's the event-in node mentioned in the help tab for uib-sender? I'm guessing this will all be more clear once the "Add a new template and example to demonstrate the sender node" TODO item is completed, so not really an issue, mostly just curious.

I did notice some weird behavior with the uib-sender node though. Here are a couple scenarios to reproduce slightly different bugs:

Setup:
image

  1. Select "Pass input msg to output?" option, hook up an inject and debug node, deploy
  2. Trigger the inject node, debug node logs the message, everything is working correctly
  3. Double click to edit the uib-sender node

Bug 1:

  1. Change nothing, click Cancel
  2. Click somewhere else to remove focus on the uib-sender node
  3. Output connection disappears but wire stays intact. There are no changes to deploy so it continues to work fine even though visually it looks broken
    image
    3a. Because there were no changes to deploy, reloading the page gets everything back to normal
  4. Repeat 1-3 if you reloaded the page. Move the uib-sender node and deploy
    image
  5. This seems like the same as above, but now messages no longer pass through and reloading the page shows the wire is actually fully disconnected now
    image

Bug 2:

  1. Change "Name" or "Topic" or even don't do anything at all, click "Done"
  2. Connection and wire both disappear, but since no changes to deploy everything still works (reloading the page shows the wires still intact)
    image
  3. If you didn't reload and now do something like move it to allow you to trigger a redeploy, now the wires are actually disconnected and no longer pass through

With both of these scenarios the "Pass input msg to output?" option stays selected the entire time. The only way to get the output connection to reappear is to deselect and select it again.

Hopefully all of this isn't too confusing, just trying to provide as much info as I can since it seems to have a few slightly different ways of behaving. Maybe next time a video will be a better idea :sweat_smile:

So it's a bit weird the way Node-RED works in Docker. I followed the official instructions for installing extra nodes. The line COPY package.json . copies it to /usr/src/node-red and installs the node_modules in that directory. So that's where socket.io lives apparently.

bash-5.0$ pwd
/usr/src/node-red

bash-5.0$ npm ls socket.io
elite-node-red@ /usr/src/node-red
`-- node-red-contrib-uibuilder@5.0.0-dev (github:totallyinformation/node-red-contrib-uibuilder#76b1368ac127cc737dc8af64d424287f05cc911f)
  `-- socket.io@4.4.0

And nothing is installed in what I believe would be the Docker version of Node-RED's userDir.

bash-5.0$ pwd
/data

bash-5.0$ npm ls socket.io
node-red-project@0.0.1 /data
`-- (empty)

bash-5.0$ cat package.json
{
    "name": "node-red-project",
    "description": "A Node-RED Project",
    "version": "0.0.1",
    "private": true
}

WIP, no docs as yet. But here is the text from the changelog:

New node uib-sender - this node allows you to send a msg to any uibuilder instance's connected front-end clients.
That means that it is pretty much the same as sending a message directly into a uibuilder node.
You select the instance of uibuilder you want to use by selecting an existing uibuilder URL from the dropdown.
You can also select whether you want input messages to go straight to the output port as well.
Or, more usefully, you can allow "return messages". This allows a front-end client to send a message to node-red with some pre-defined metadata added that will route the message back to the uib-sender node. In this way, the sender node can be used as a semi-independent component.
Note that this same method can be used by ANY custom node, check out the code to see how it works. It requires the use of an external, shared event module @TotallyInformation/ti-common-event-handler. The msg metadata looks like: { _uib: {originator: <sender_node_id>}, payload: ... }. The sender node id is just that, the Node-RED node id for the sender node instance.
The uibuilderfe.js library has been updated to allow easy use of the originator property for uibuilder.send(). See below for details.

So you can use it as an alternative to link nodes if you want to send data into your uibuilder-powered front-end. Whereas a link node requires you to link an in and out link together manually, the sender node only needs to know what uibuilder url you want to use and everything gets routed automatically (it doesn't really, it uses JavaScript/Node.js EVENTs which is much more efficient).

Ah, that needs removing. I realised late on that only the 1 node is needed. There were originally a matching pair. But instead, I built the return capability right into the uibuilder node so that it is trivial to get a message back to the sender simply by using the metadata in the msg you send:

{
    "payload": "OOH! A return message :-)",
    "_uib": {
        "originator": "85fee74096237ff3"
    }
}

:slight_smile: Added to the fix list, thanks.

No, not at all, the testing is much appreciated and is greatly helping me get back into working on the nodes.

Hmm, maybe. I don't do videos though :sunglasses: Perhaps one day.

1 Like

Oh poo! That is not good. But I probably should have thought of it. That's the problem with node.js and Node-RED, there are multiple locations things can be installed and npm has no robust way of finding things no matter where they are installed, you kind of have to know at least some things. A right pain!

It would be interesting to see if Node-RED was happy with things installed there though - I think it would be. It is a pain in the butt to have things effectively installed globally which is what the instructions are doing.

Well, OK, thanks for the info. At least we know that things still work when installed that way. It is just a warning message really anyway so I can make some simple ammendments to it.

Oh nice, I somehow missed that part.

Got it, that seems like a much cleaner solution than link nodes.

I meant I should've done a video instead of spending an hour figuring out how to describe all that in words.

Hmm, I'll give it a shot.

1 Like