uiBuilder - Share your dash

Looked through the forum but could not find a post where individual interfaces was shared , so if there are a "sticky" post Ill gladly move this one.
Thank you for the super work that was done with uiBuilder! I thought I would share what I have done iuiBuilder.
I started with the basic example that was given on the forum and then started building it up from there.
I am using Vue , Bootstrap-vue , tabulator and jquery for the backbone.
Hope it gives some inspiration.




9 Likes

Great idea for a thread Paul!

Here's a complex bootstrap-vue table with editable fields - displaying my network records. A file is used to retain the records and nmap gets the latest data live from the network. At the end of the nmap script, it saves the XML tmp file and calls a simple node-red http endpoint. Node-RED then reads the XML, updates the file. The network table shows the file and can update it.

This is from a home heating dashboard that is well overdue for some TLC - currently uses my old wiser module but needs changing over to the new wiser node so not everything working at the moment.

Another one due for some work - simple card interface showing and controlling lights. Note that I've not done ANY visual stuff on this page other than using bootstrap-vue card components. Quite a good illustration of what things look like without any CSS work.

A raw Apex chart example:

And a clip from my custom Node-RED logger:

That'll do for now.

7 Likes

Thanks, @Paulf007! This is very timely in my case as I'll have to ditch the dashboard I'm currently using.

I've looked at uibuilder briefly but it appeared to be much more complex than I care for. I see the list of packages you use but I'm not familiar with any of them so I'll appreciate some sort of difficulty/learning curve/time involved info so I can decide if it's the way I want to go.

I've been coding since '81 but I never needed more than the basics of HTML and CSS.... well, :exploding_head: so you have some idea where I'm starting.

I like what you've done very much so I'll want to copy much of it if you're willing to share.

Once you get going , you almost cant stop ...
Also got a Network scanner running. It scans the network every 5 min via nmap and if there is a new device on the network it will add it and then notify me.

I also capture the Mac address in a sqlite db where I then can assign a name. Once I have linked the name to the mac it will automatically link it next time it get scanned.
Just for eye candy I also have a map that gets generated from the network db.

Nmap also can do a advanced scan of the device and place it in a XML report.

The Network Map of all the devices

8 Likes

Difficulty - quite steep , for me anyway :slight_smile:

What I ended up doing is to get a template , from the forum , with a switch and input.
Once I had the framework , I started to setup a basic switch and then added the info what I wanted.
From there it was copy and paste.
It is very difficult to share the whole setup as it is very dependent on other inputs and rules at the backside.
Here is some screen grabs of my Node Red Admin side to give you an idea.

uiBuilder Controle page ;
I suspect that there are many a better way to do this but it has served me well sofar.


I have a page with all my relays where I use the link node to send and receive commands around that way I can track problems should they arrive.


and the same with my input nodes

Apart form a couple of Tasmota devices I use a at Mega to control relays from a central point. It gives me 24 relays and 32 inputs.

Here is a page where I control all the lights automation.

1 Like

Good to know my 30,000' assessment wasn't too far off.

I understand. I'm already doing all my automation processing in NR so I was asking about the uibuilder-specific code but I'll start where you did and see how far I get.

Thanks!

What did you use to draw the Network Map (as it looks very impressive) ??
Is that part of uBuilder??

2 Likes

Well you have more than some already :slight_smile:

VueJS is one of the easiest of the front-end frameworks but it still has a fairly involved learning curve. I tend to use it with bootstrap-vue which integrates the Bootstrap CSS framework and adds some extra useful components (custom HTML tags). That means that you can get something quite nice looking with minimal code. You will then spend an inordinate amount of time finessing things :rofl:

If you want to avoid such a steep learning curve, you could simply start with just jQuery which gives you a more procedural approach. It is easier to learn but in the end, it leaves you writing a fair bit more JavaScript code. The advantage of a framework like VueJS is the fact that you don't need to write code to handle display changes. As long as you are using a defined variable (defined in the data section of the vue object in JavaScript), any changes you make to that variable - e.g. updating from an incoming msg from Node-RED - will be reflected in the browser. Another advantage is the many custom components - though many of them only describe how to use them with a build step (where you have to run a script to translate the code into something the browser directly understands) and that is yet another learning curve. Though if you look in the uibuilder tech docs for the vNext branch, you will see that I've expanded on about the simplest method for a build step using a tool called Snowpack.

If you can just keep to native Vue and boostrap-vue, the initial learning curve shouldn't be too bad.

I think you may be very pleased with the next major release. It has 2 new nodes included. One for caching data and the other for sending data direct to a uibuilder node from a different flow (though obviously you can also simply still use a link node). The config layout for uibuilder itself is also a lot neater and easier in the new version.

I recommend building something useful but simple to begin with. Like my lights dashboard example or even something that just displays a couple of outputs and maybe takes an input. Because the basics really are quite simple as you'll see from the example templates I provide.

Haha, I wish! I suspect the use of a library. Never bothered to do that with my own network data as the connectivity is actually pretty simple.

1 Like

No. That part is from vis.js network map. Quite fun to play with..

Looks like there are quite a few options for using the vis.js network diagram with VueJS as well :slight_smile:

Hi Paul,
Did you use vis.js in a Node-RED flow or did you run it outside of NR as a standalone package??
I really like the Raspberry icon you have used to identify the RPi on the network - very creative.

Is you network chart interactive?? Can you click on a node and get taken to the node's IP address??

VueJS is one of the easiest of the front-end frameworks but it still has a fairly involved learning curve. I tend to use it with bootstrap-vue which integrates the Bootstrap CSS framework and adds some extra useful components (custom HTML tags).

I would argue that tailwindcss + alpine.js lowers that curve quite a bit, then again for any chosen path, knowledge of html/css/javascript are prereqs.

I dont mind sharing the flow. But you will see that I pull the network data from memory and the "create" the html file that is the presented to html file.

[
    {
        "id": "d9113cb2.53b57",
        "type": "http in",
        "z": "e5166ae906599ee8",
        "name": "",
        "url": "/networkGraph",
        "method": "get",
        "upload": false,
        "swaggerDoc": "",
        "x": 190,
        "y": 1300,
        "wires": [
            [
                "d92661d5.e8b18",
                "c410078a.563e78"
            ]
        ]
    },
    {
        "id": "d92661d5.e8b18",
        "type": "function",
        "z": "e5166ae906599ee8",
        "name": "Setup Data",
        "func": "host = global.get(\"host\")\nDIR = \"http://\"+host+\":1880/icons/\";\nobj = global.get(\"network\");\ndns = global.get(\"dns\")\n\n var static = [\n   { id: 5,    label: \"Local - \"+dns    ,image: DIR + \"generic.png\"       ,shape: \"image\",level:2 },\n\n\n ]\n  \n  \nvar EDGE_LENGTH_MAIN = 250;\nvar EDGE_LENGTH_MAIN2 = 150;\nvar EDGE_LENGTH_SUB = 80; \nvar EDGE_LENGTH_SUB2 = 10; \nvar EDGE_LENGTH_MAIN3 = 300; \n\nvar EDGE_WiTH_MAIN = 5\nvar EDGE_WiTH_LV2 = 3\n  \n\n\n// Setup Nodes from Network\nlocationSetup   = extractUnique(obj,'location').sort()\n              //  var diff = {payload:locationSetup};\n              //  node.send(diff); \nlocation        = setUpNetwork(locationSetup,100,4)\n\nrooms_Create    = roomsSetup_f(obj)\nroomsSetup      = extractUnique(rooms_Create ,'room').sort()\nrooms           = setUpNetwork(roomsSetup,500,4)\ndevices         = nodeAdd(obj);\n\n\nnodes = static.concat(location).concat(rooms).concat(devices);\n\n\n// Setup Links Devices - IP Address\n    house_location  = createLinkLocation    (location,5,'id',6)\n    location_room   = link_room_location    (rooms_Create,rooms,location,3)\n    room_devices    = link_room_devices     (obj,rooms,2)\n    \n\n\n\nfunction link_room_devices (arr,arr2,width_data){     // This is to account to for the buildings that does not have rooms\nr = host.split(\".\")\nreplaceVal =r[0]+\".\"+r[1]+\".\"+r[2]+\".\"\n\n    var y1 = [];\n        for (var j = 0; j < arr.length; j++) {\n            if (arr[j][\"room\"] == \"none\"){\n                rm = arr[j][\"location\"]+\"- Room 1\"\n                }else{\n                rm = arr[j][\"room\"]\n                 }\n                    rmID      =  search(rm,'label',arr2,'id')   \n                    devID     =  (arr[j][\"Ip\"].replace(replaceVal,'')*1)+200 \n                    \n                        i = {\n                            from:  rmID ,\n                            to  :  devID  ,   \n                            width : width_data\n                            }\n                \n                 \n                 y1.push(i);\n            }\n        return y1\n    }\n    \n\n\n\n\n\n//devices_link  = link_room(obj,location,rooms,3);\n\nedges = house_location.concat(location_room).concat(room_devices);\n\n\n\nmsg.rooms = [room_devices,devices,rooms_Create,rooms,location]\n\n\n\n\nmsg.edges = JSON.stringify(edges);   \nmsg.nodes = JSON.stringify(nodes);  \n\n   txt1 = global.get(\"navBar\");\n        i = txt1.indexOf(\"href=\\\"http://\"+host+\":1880/networkGraph\")-2\n        msg.navBar =txt1.slice(0, i) + \" active \" + txt1.slice(i);\n  \n\nreturn msg;\n\n\n\n\n// ------------------------------ Funtions --------------------------------------------//\nfunction search(nameKey,key, myArray,val){\n    for (var i=0; i < myArray.length; i++) {\n        if (myArray[i][key] === nameKey) {\n            return myArray[i][val];\n        }\n    }\n}\n\n\nfunction link_room_location (arr1,arr2,arr3,width_data){\n    var y1 = [];\n        for (var j = 0; j < arr2.length; j++) {\n            rm = arr2[j].label\n            loc = search(rm,'room',arr1,'location')  \n           // loc = arr1[j].location \n                rmID    =  arr2[j].id //search(rm,'label',arr2,'id')\n                locID   =   search(loc,'label',arr3,'id')\n                    i = {\n                        from:  locID ,\n                        to  :  rmID ,   \n                        width : width_data\n                        }\n            \n             y1.push(i);\n        }\n    return y1\n    }\n\n\nfunction roomsSetup_f(arr){     // This is to account to for the buildings that does not have rooms\n    var y1 = [];\n        for (var j = 0; j < arr.length; j++) {\n        \n            if (arr[j][\"room\"] == \"none\"){\n            i ={room: arr[j][\"location\"]+\"- Room 1\",location:arr[j][\"location\"]}\n                \n            }else{\n            i = {room:arr[j][\"room\"],location:arr[j][\"location\"]}    \n                 }\n                 y1.push(i);\n            }\n        return y1\n    }\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nfunction arryFindVal (arr,val){\n var i = arr+\";\"+val\n //let newI = arr.filter(val);\n return i\n \n //   return (arr.indexOf(val) != -1);\n \n}\n\n\nfunction createLinkLocation (arr,link1,link2,width_data){\n var y1 = [];   \nfor (var j = 0; j < arr.length; j++) {\n    \n    link = arr[j][link2]\n    \n   i = { from:link1 , to: link , width : width_data }\n\n   y1.push(i);\n}\nreturn y1\n   \n   \n    \n}\n\n\n\n\n\nfunction compaireArr (arr,str,alt){\n if (arr.includes(str) == true){\n     return str\n }  else {\n     return alt\n } \n}\n\n\nfunction setUpNetwork(arr,beginVal,lev,arr2){\nvar png = flow.get(\"png\")\nvar y1 = [];\nhost = global.get(\"host\")\nDIR = \"http://\"+host+\":1880/icons/\";\nfor (var j = 0; j < arr.length; j++) {\n    \n                //var diff = {payload:{arr:arr[j],full:arr}}; // Building has a unidentified item\n                //node.send(diff); \n            \n    if (arr[j] !== null){\n    \n   img = compaireArr(png,arr[j].toLowerCase().replace(/ /g,'')+\".png\",\"generic.png\")\n    }\n  //   img = compaireArr(png,arr[j]+\".png\",\"Generic.png\")   \n   i = {\"id\":(j+beginVal) ,\"label\":arr[j],image: DIR +img,shape: \"image\",level:lev }\n\n   y1.push(i);\n}\n            //    var diff = {payload:y1};\n            //    node.send(diff); \n            \nreturn y1\n} \n\n\n\n\nfunction nodeAdd(arr){\nvar y1 = [];\n// { id: 106, label: \"External\",image: DIR + \"exsternal.png\",shape: \"image\",level:1 },\nDIR = \"http://\"+host+\":1880/icons/\"\n//host = global.get(\"host\")\nr = host.split(\".\")\nreplaceVal =r[0]+\".\"+r[1]+\".\"+r[2]+\".\"\n//var diff = {payload:replaceVal}; // Building has a unidentified item\n//node.send(diff);\nfor (var j = 0; j < arr.length; j++) {\n\n //n = 200+j\n   i = {\"id\":(arr[j][\"Ip\"].replace(replaceVal,'')*1)+200 ,\"label\":arr[j][\"name\"]+\"\\n\"+arr[j][\"Ip\"],image: DIR +getImg( arr[j][\"device\"]),shape: \"image\",level:1 }\n   y1.push(i);\n}\nreturn y1\n}\n\n\n\n\n\n\nfunction extractUnique (data,group){            //  Only One Value\n    let unique = [...new Set(data.map(item => item[group]))];\n    return unique\n}\n\n\n\nfunction extractArry(arr,val){\nvar y1 = [];\n\nfor (var j = 0; j < arr.length; j++) {\n\n //n = 200+j\n   i = [arr[j][val]]\n   y1.push(i);\n}\nreturn y1\n}\n\n\nfunction getImg(dev){\n if (dev == undefined){\n     \n }else{\n    i =dev.toLowerCase().replace(/ /g,'')+\".png\"\n    return i  \n }\n\n}\n\n\n\n\n\n\n\n\n\n",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 430,
        "y": 1300,
        "wires": [
            [
                "91114dd6.1e13e"
            ]
        ]
    },
    {
        "id": "91114dd6.1e13e",
        "type": "template",
        "z": "e5166ae906599ee8",
        "name": "vis.js network map",
        "field": "payload",
        "fieldType": "msg",
        "format": "handlebars",
        "syntax": "mustache",
        "template": "\n<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <title>Network Map</title>\n        <link rel=\"icon\" type=\"image/png\" href=\"https://www.freeiconspng.com/uploads/network--wifi--wireless-icon-10.png\"/>\n        <link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css\">\n        <script src=\"https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.3/umd/popper.min.js\"></script>\n        <script src=\"https://code.jquery.com/jquery-3.6.0.min.js\"></script>\n        <script src=\"https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/js/bootstrap.min.js\"></script>\n    <style type=\"text/css\">\n    html, body, #container {\n        width: 100%;\n        height: 100%;\n        margin: 0;\n        padding: 0;\n      }\n    \n      \n\n      #mynetwork {\n        width: 1800px;\n        height: 1400px;\n        border: 1px solid lightgray;\n      }\n    </style>\n\n    <script\n      type=\"text/javascript\"\n      src=\"https://unpkg.com/vis-network/standalone/umd/vis-network.min.js\"\n    ></script>\n\n\n </head>\n \n<body>\n <div class=\"card-body\">\n {{{navBar}}}\n \n\n\n<div id=\"mynetwork\"></div>\n<script type=\"text/javascript\">\n\n//var DIR = \"http://192.168.8.30:1880/icons/\";\n\n\n  // create an array with nodes\n  var nodes = new vis.DataSet({{{nodes}}});\n\n  // create an array with edges\n  var edges = new vis.DataSet({{{edges}}});\n\n  // create a network\n  var container = document.getElementById(\"mynetwork\");\n  var data = {\n    nodes: nodes,\n    edges: edges\n  };\nvar options = {\n       edges:{\n            arrows: 'from,to',\n           // color: 'red',\n          //  font: '12px arial #ff0000',\n            scaling:{\n              label: true,\n            },\n            shadow: true,\n            smooth: true,\n          },\n           \n    \n      physics:{\n        enabled : true,\n            \"barnesHut\": {\n      \"gravitationalConstant\": -3000\n    },\n         hierarchicalRepulsion: {\n              centralGravity: 0.0,\n              springLength: 500,\n              springConstant: 0.06,\n              nodeDistance: 250,\n              damping: 0.09,\n              avoidOverlap: 1\n            },\n      },\n}\n  var network = new vis.Network(container, data, options);\n</script>\n\n</div>\n  </body>\n</html>",
        "output": "str",
        "x": 690,
        "y": 1300,
        "wires": [
            [
                "a2d7de35.d15a6"
            ]
        ]
    },
    {
        "id": "a2d7de35.d15a6",
        "type": "http response",
        "z": "e5166ae906599ee8",
        "name": "",
        "statusCode": "",
        "headers": {},
        "x": 930,
        "y": 1300,
        "wires": []
    },
    {
        "id": "c410078a.563e78",
        "type": "exec",
        "z": "e5166ae906599ee8",
        "command": "ls $HOME/data/html/icons/",
        "addpay": "",
        "append": "",
        "useSpawn": "false",
        "timer": "",
        "winHide": false,
        "oldrc": false,
        "name": "List Png Files",
        "x": 430,
        "y": 1360,
        "wires": [
            [
                "74cef741.e7cdb8"
            ],
            [],
            []
        ]
    },
    {
        "id": "74cef741.e7cdb8",
        "type": "function",
        "z": "e5166ae906599ee8",
        "name": "flow.set-png",
        "func": "context.set(\"png\",msg.payload.split('\\n'))\na = context.get(\"png\").slice(0,-1)\nvar PATTERN = '.png',\nnames = a.filter(function (str) { return str.indexOf(PATTERN) > -1; });\nvar lowercased = names.map(name => name.toLowerCase());\n\n\n\nflow.set(\"png\",lowercased)\n\nmsg.payload = lowercased\n\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 640,
        "y": 1360,
        "wires": [
            []
        ]
    },
    {
        "id": "3f13f4ec.491dac",
        "type": "watch",
        "z": "e5166ae906599ee8",
        "name": "Update if Change",
        "files": "data/html/icons/",
        "recursive": "",
        "x": 180,
        "y": 1400,
        "wires": [
            [
                "c410078a.563e78"
            ]
        ]
    }
]

Hope it helps!

4 Likes

Tailwind is something I need to have a play with for sure. Unfortunately, it isn't anywhere near as easy to use with VueJS as bootstrap is with the bootstrap-vue library as that combination doesn't require any other installs and doesn't require any kind of build step (which is why I chose bootstrap-vue over other, similar libraries).

As for alpine.js - I'd have to say that, at first sight, I personally dislike it a lot. It doesn't provide any components, doesn't seem to work in a W3C component compatible way (though maybe I've misunderstood) and it isn't immediately obvious how to integrate JS code and dynamic variables which is the most important strength of something like Vue. Again though, maybe I've just not understood things from their website.

Still, there is always room on the uibuilder WIKI for more examples on how to use various frameworks, so if you wanted to contribute something, that would be great! :wink:

I think that the best alternative to VueJS that I've found is Svelte, it is very clean, works in a very W3C components compatible way and includes a sensible build process with a dev server that happily works with Node-RED and uibuilder. It may not have the range of 3rd-party components and examples as Vue though. Without a doubt, you can expect to see a future template that uses Svelte and will show off the future capabilities of running npm scripts and an integrated dynamic reload server from within the Editor.

What is clear from this though is that there isn't a one-size-fits-all front-end framework. And that is, after all, one of the reasons uibuilder was born and why it is designed the way it is! It doesn't make any assumptions about what (if any) framework to use and simply provides some heavy lifting to help you easily create data-driven web apps.

Tailwind is something I need to have a play with for sure. Unfortunately, it isn't anywhere near as easy to use with VueJS as bootstrap is with the bootstrap-vue library as that combination doesn't require any other installs and doesn't require any kind of build step (which is why I chose bootstrap-vue over other, similar libraries).

Actually tailwind has a JIT compiler available, it is a matter of including <script src="https://cdn.tailwindcss.com"></script> and it works straight away, it is quite awesome.

For alpinejs, it acts like components with templates where you can attach an x-text or x-html attribute where it will inject reactive data (like a slot), properties and data can be changed on the fly and the dom reacts.

Yes components, as in separate files are not available. Then again I can imagine someone will come up with something similar like http-vue to make that possible. Svelte is indeed awesome too, but i don't want to run scripts/build stuff separately, I just want stuff to work straight away.

1 Like

Can I ask if the various devices on your network all use fixed IP addresses??

No , that was one reason why I started the network scanner project , to keep track of the mac addresses rather than the Ip's.
Although the devices that I do need to have regular access to , like octo print , node red has a static IP's also I upgraded to a Mikrotik Router so I linked the most of the devices to a "fixed" ip in the router.

I've been waiting to see more dashboards, ya know, like the OP wrote in the title to this topic? :wink:

I need to agree , this did went a little bit off topic :rofl:
Please share more !

1 Like

I was working on a house alarm system, but at the moment I am being pulled to a smart home solution. I did make some screens I liked and there was some stuff I thought was good. All settings were programmable and written to a Global file.
This is my 'normal screen', I included date and time because my Mum had Alzheimer's and it was useful for her, at least for a while. I also wanted it to show weather but that would have meant using a non standard functions. My plan was to have it display output from a camera if motion was detected.

Then the screen where you can set/reset the alarm

The basic settings for the alarm

It got interesting when I wanted data to populate as a screen loaded. All setting are written to a global file. The list of Zones (Full Set etc) is loaded from a file. Then you select a Zone from the drop down list, which then pulls in the settings for the selected zone. You can edit the settings and then write them back to the file.

The Log file for alarm history was surprisingly easy.

I managed to get the set/reset screen to automatically adjust to any screen size.
Small

Big

The Alarm screen displayed automatically, no matter which screen is displayed before the alarm happened.
node-red alarm screen

I did find I was fighting Bootstrap Vue to get the layout as I wanted it, so thing aren't centred as I would like them to be. But it ended up OK. I think I broke the uibuilder rules and had 6 separate uibuilder pages and switched between them.
If anybody would like the Flows, let me know. The alarm was finished apart from the Exit Timer.

4 Likes