Duplicate flows automated in runtime

Hey, I'm currently working on a project to get and save data from a control device. I've set up everything in the dashboard, so you can do measurements, save the data into a database, convert the data to csv, download it, delete and write content to the database, change ip addresses and so on. What I want to do now is to add the functionality so the user can duplicate the whole flow (every flow shown in the navbar) to have access to multiple control devices with different ip addresses. My goal is that every control device has its own tab in the dashboard. But I dont know how to set it up so the user can dynamically expand the dashboard without touching the editor.

I already thought about writing a bashscript, which duplicates the JSON, generates new IDs per Node (so it will actually appear in the project) and start Node-Red with that new file again. But it would but much, much work because of the size of the project. So my question is, isn't there an easier way to get that work done? I mean if you manually import flows, the IDs getting changed automated. Can I somehow use this functionality as a user?

Thanks

Edit: I just read about something like PUT /flow. Is that maybe what I'm searching for? If yes, how does it work?

HI and welcome to the forum.

I would suggest you copy all the code, open a new tab, paste on it and then edit all the dashboard nodes and put them in/on their own tab.

I know it is a bit slow, but I think you would be better doing it that way.

Thanks for you answer. But I dont want the user to touch the editor. All should be done within the dashboard.

You cannot. Tabs are configured within the editor.

Have a look this will load all the flows into the editor
and upload them to the file system.

Note;
after you edit the msg and upload it, NR needs to deploy/ restart before change will be seen in Dashboard


[
    {
        "id": "2756ff49.7a67",
        "type": "debug",
        "z": "7952336614d75e58",
        "name": "",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "true",
        "x": 1030,
        "y": 220,
        "wires": []
    },
    {
        "id": "b93f6d0a.9aca3",
        "type": "inject",
        "z": "7952336614d75e58",
        "name": "",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "{}",
        "payloadType": "json",
        "x": 110,
        "y": 120,
        "wires": [
            [
                "b96df551.e84528"
            ]
        ]
    },
    {
        "id": "e2cfaaa6.081f68",
        "type": "http request",
        "z": "7952336614d75e58",
        "name": "POST /auth/token",
        "method": "POST",
        "ret": "txt",
        "paytoqs": "ignore",
        "url": "http://localhost:1880/auth/token",
        "tls": "",
        "persist": false,
        "proxy": "",
        "authType": "",
        "senderr": false,
        "headers": [],
        "x": 450,
        "y": 120,
        "wires": [
            [
                "ec48a95e.ec3ee8"
            ]
        ]
    },
    {
        "id": "b96df551.e84528",
        "type": "change",
        "z": "7952336614d75e58",
        "name": "",
        "rules": [
            {
                "t": "set",
                "p": "payload.client_id",
                "pt": "msg",
                "to": "node-red-editor",
                "tot": "str"
            },
            {
                "t": "set",
                "p": "payload.grant_type",
                "pt": "msg",
                "to": "password",
                "tot": "str"
            },
            {
                "t": "set",
                "p": "payload.scope",
                "pt": "msg",
                "to": "*",
                "tot": "str"
            },
            {
                "t": "set",
                "p": "payload.username",
                "pt": "msg",
                "to": "YOUR USER NAME",
                "tot": "str"
            },
            {
                "t": "set",
                "p": "payload.password",
                "pt": "msg",
                "to": "YOUR PASSWORD",
                "tot": "str"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 260,
        "y": 120,
        "wires": [
            [
                "e2cfaaa6.081f68"
            ]
        ]
    },
    {
        "id": "d5b253f4.a73b9",
        "type": "change",
        "z": "7952336614d75e58",
        "name": "",
        "rules": [
            {
                "t": "set",
                "p": "token",
                "pt": "flow",
                "to": "payload",
                "tot": "msg"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 920,
        "y": 120,
        "wires": [
            [
                "672dcc9f.b60984"
            ]
        ]
    },
    {
        "id": "ec48a95e.ec3ee8",
        "type": "json",
        "z": "7952336614d75e58",
        "name": "",
        "property": "payload",
        "action": "",
        "pretty": false,
        "x": 610,
        "y": 120,
        "wires": [
            [
                "77702c60.df33e4"
            ]
        ]
    },
    {
        "id": "df2e5d9c.3664a",
        "type": "http request",
        "z": "7952336614d75e58",
        "name": "GET /flows",
        "method": "GET",
        "ret": "obj",
        "paytoqs": "ignore",
        "url": "http://localhost:1880/flows",
        "tls": "",
        "persist": false,
        "proxy": "",
        "authType": "",
        "senderr": false,
        "headers": [],
        "x": 510,
        "y": 220,
        "wires": [
            [
                "398be1ed.e0ffce"
            ]
        ]
    },
    {
        "id": "672dcc9f.b60984",
        "type": "change",
        "z": "7952336614d75e58",
        "name": "",
        "rules": [
            {
                "t": "delete",
                "p": "headers",
                "pt": "msg"
            },
            {
                "t": "set",
                "p": "headers.Authorization",
                "pt": "msg",
                "to": "token.access_token",
                "tot": "flow"
            },
            {
                "t": "set",
                "p": "headers.Node-RED-API-Version",
                "pt": "msg",
                "to": "v2",
                "tot": "str"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 320,
        "y": 220,
        "wires": [
            [
                "df2e5d9c.3664a"
            ]
        ]
    },
    {
        "id": "398be1ed.e0ffce",
        "type": "change",
        "z": "7952336614d75e58",
        "name": "",
        "rules": [
            {
                "t": "delete",
                "p": "headers",
                "pt": "msg"
            },
            {
                "t": "set",
                "p": "headers.Authorization",
                "pt": "msg",
                "to": "token.access_token",
                "tot": "flow"
            },
            {
                "t": "set",
                "p": "headers.Node-RED-API-Version",
                "pt": "msg",
                "to": "v1",
                "tot": "str"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 720,
        "y": 240,
        "wires": [
            [
                "f03c48f9.385fc8"
            ]
        ]
    },
    {
        "id": "ddff717.ad4279",
        "type": "comment",
        "z": "7952336614d75e58",
        "name": "Change user/pass below",
        "info": "",
        "x": 260,
        "y": 80,
        "wires": []
    },
    {
        "id": "77702c60.df33e4",
        "type": "function",
        "z": "7952336614d75e58",
        "name": "Bearer",
        "func": "msg.payload.access_token = \"Bearer \" + msg.payload.access_token\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 750,
        "y": 120,
        "wires": [
            [
                "d5b253f4.a73b9"
            ]
        ]
    },
    {
        "id": "f03c48f9.385fc8",
        "type": "http request",
        "z": "7952336614d75e58",
        "name": "POST /flows",
        "method": "POST",
        "ret": "txt",
        "paytoqs": "ignore",
        "url": "http://localhost:1880/flows",
        "tls": "",
        "persist": false,
        "proxy": "",
        "authType": "",
        "senderr": false,
        "headers": [],
        "x": 890,
        "y": 220,
        "wires": [
            [
                "2756ff49.7a67"
            ]
        ]
    }
]

Well.. So I have to get it done with a bash script? I successfully created a small prototype.. so that would be an option

Can you please tell me more about that? I can not see any changes after executing.
I'm just getting the following output: "Unexpected token < in JSON at position 0"

Heres the bash script prototype. Just run it, it closes node-red and edit the existing file. It will generate more and more tabs, just an example, of what I'm trying to do. Of couse that script could be triggered by a button or whatever. You will need an existing node-red project named "testflow.json" in the same folder as the script.

#!/bin/bash

var=$(echo $RANDOM | md5sum | head -c 16)
inject=$(echo $RANDOM | md5sum | head -c 16)
debug=$(echo $RANDOM | md5sum | head -c 16)
ui_tab=$(echo $RANDOM | md5sum | head -c 16)
ui_group=$(echo $RANDOM | md5sum | head -c 16)
spacer=$(echo $RANDOM | md5sum | head -c 16)

pkill node-red
sed -i '$ d' testflow.json
cat << EOF >> testflow.json
    ,{
        "id": "${var}",
        "type": "tab",
        "label": "Flow",
        "disabled": false,
        "info": "",
        "env": []
    },
    {
	"id": "${ui_tab}",
	"type": "ui_tab",
	"name": "Test",
	"icon": "dashboard"
    },
    {
	"id": "${ui_group}",
	"type": "ui_group",
	"name": "Test",
	"tab": "${ui_tab}",
	"order": 1,
	"disp": true,
	"width": "6",
	"collapse": false,
	"className": ""
    },
    {
	"id": "${spacer}",
	"type": "ui_spacer",
	"z": "${var}",
	"name": "spacer",
	"group": "${ui_group}",
	"order": 1,
	"width": 1,
	"height": 1
    },
    {
        "id": "${inject}",
        "type": "inject",
        "z": "${var}",
        "name": "",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "x": 280,
        "y": 200,
        "wires": [
            [
                "${debug}"
            ]
        ]
    },
    {
        "id": "${debug}",
        "type": "debug",
        "z": "${var}",
        "name": "debug 2",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "false",
        "statusVal": "",
        "statusType": "auto",
        "x": 500,
        "y": 200,
        "wires": []
    }
]
EOF

node-red testflow.json

exit

I'm just getting back to my project and love this script. Very nice job!
I'm not great at reading other people's work, but it looks like these are random-generated numbers being swapped in to a template, which is then added to the flow file.

Is that accurate?
I'm going to test it in my sandbox right now, but just wanted to say nice job.

edit: in the end, this script didn't quite fit my needs, but it got me pointed in the right direction. Thanks.

1 Like

Thank you for you recognition!
Exactly! Because every node has its own ID, you can't just copy and paste existing nodes into the same project. They wont be accessed this way. That's why we have to change these to unique strings.

I could do some improvements at the accuraty. $RANDOM generates a random number from 0 to 32767. So it could be possible to generate an existing string because for example the md5sum of 12345 is the same as the md5sum of another 12345 (thats actually why the md5 algorythm is so important for file transfers etc.). I'm just using the md5sum right here to get a total of 16 chars.
Of course we could multiplie two random numbers so we have a range from 0 to 1.073.676.289 [var=$(echo $RANDOM*$RANDOM | md5sum | head -c 16)].

If you need help at your project, just hit me up!

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.