Did anyone implement this? A flow to export all flows, as indivdiual files?
If so, maybe post it? Would like to avoid reinventing the wheel, thanks.
Did anyone implement this? A flow to export all flows, as indivdiual files?
If so, maybe post it? Would like to avoid reinventing the wheel, thanks.
I do this all the time with flowhub.org - I created it so that I could modularise my flows into separate flows and store (and reuse) them individually. If you install the flowhub nodes you get a list of example flows that I created as examples. This can be included into NR by just double clicking on them - for ease of installation.
FlowHub.org isn't open source but can be used by anyone with a github account. If anyone has too much money and wants this to be open source, then I am willing to take their too much money monies and create an open source solution.
Interesting what you suggest... Definitely going to look at flowhub.org. But specific to my original plan, or use case, back up of individual flows I tried the following:
[{"id":"c3c63d388925a619","type":"tab","label":"Export","disabled":false,"info":"","env":[]},{"id":"8e0aa9a17a268478","type":"inject","z":"c3c63d388925a619","name":"Export","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":130,"y":60,"wires":[["dcd9312ec34c44c3"]]},{"id":"1bb33d5da61ee17d","type":"http request","z":"c3c63d388925a619","name":"Flows","method":"GET","ret":"txt","paytoqs":"body","url":"","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":390,"y":60,"wires":[["042927082d1131a9"]]},{"id":"dcd9312ec34c44c3","type":"change","z":"c3c63d388925a619","name":"Resource","rules":[{"t":"set","p":"url","pt":"msg","to":"http://localhost:1880/flows","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":260,"y":60,"wires":[["1bb33d5da61ee17d"]]},{"id":"e227c1d5b36ed3d6","type":"function","z":"c3c63d388925a619","name":"List","func":"if (Array.isArray(msg.payload)) {\n msg.flows = msg.payload\n .filter(item => 'type' in item && item.type === 'tab')\n .map(item => ({\n label: item.label,\n id: item.id\n }));\n}\n\nreturn msg;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":650,"y":60,"wires":[["119bd9b989aca720"]]},{"id":"042927082d1131a9","type":"json","z":"c3c63d388925a619","name":"Convert","property":"payload","action":"obj","pretty":false,"x":520,"y":60,"wires":[["e227c1d5b36ed3d6"]]},{"id":"119bd9b989aca720","type":"array-loop","z":"c3c63d388925a619","name":"Flows","key":"flow","keyType":"msg","reset":true,"resetValue":"value-null","array":"flows","arrayType":"msg","x":790,"y":80,"wires":[[],["fd78953fd50dc364"]]},{"id":"cd342945bdc6ee11","type":"http request","z":"c3c63d388925a619","name":"Flow","method":"GET","ret":"txt","paytoqs":"body","url":"","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":910,"y":160,"wires":[["7940d1eca92f8dcf"]]},{"id":"fd78953fd50dc364","type":"function","z":"c3c63d388925a619","name":"Get","func":"msg.payload = `http://eccentric-ethernet:1880/flow/${msg.flows[msg.flow].id}`\nnode.warn(msg.payload)\n\nflow.set('Path', `/tmp/${msg.flows[msg.flow].label}.json`)\n\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":790,"y":160,"wires":[["cd342945bdc6ee11"]]},{"id":"6bb549e9cbf6eae9","type":"file","z":"c3c63d388925a619","name":"Write","filename":"path","filenameType":"msg","appendNewline":true,"createDir":false,"overwriteFile":"true","encoding":"none","x":1150,"y":160,"wires":[["119bd9b989aca720"]]},{"id":"7940d1eca92f8dcf","type":"change","z":"c3c63d388925a619","name":"Set","rules":[{"t":"set","p":"path","pt":"msg","to":"Path","tot":"flow"}],"action":"","property":"","from":"","to":"","reg":false,"x":1030,"y":160,"wires":[["6bb549e9cbf6eae9"]]},{"id":"4f8174a0453f9d0c","type":"global-config","env":[],"modules":{"node-red-contrib-loop-processing":"0.5.1"}}]
The HTTP request to get all flows, and then parse for flow names and ids seems to work. I can curl the URL using flow id and I seem to get the individual flow.
But, I cannot seem to get the HTTP request to get an individual flow to work in NR. Even when I pass in the correct flow id, the HTTP response returns the entire set of flows, not the single flow I desire.
The Node Red Admin API states that GET /flow/: is the correct syntax? The admin API page shows a ':' before the id value? That does not seem to make any sense, and fails to work anyway, via curl. Removing the ':' the URL via curl works. So what I am missing with the HTTP request node in NR, that it fails, returing all flows?
Not sure how to ensure the individual flow files are pretty format as well as yet.
No sooner do I post the question, I realized the problem... Corrected flow below...
[{"id":"c3c63d388925a619","type":"tab","label":"Export","disabled":false,"info":"","env":[]},{"id":"8e0aa9a17a268478","type":"inject","z":"c3c63d388925a619","name":"Export","props":[{"p":"url","v":"http://eccentric-ethernet:1880/flows","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":130,"y":60,"wires":[["1bb33d5da61ee17d"]]},{"id":"1bb33d5da61ee17d","type":"http request","z":"c3c63d388925a619","name":"Flows","method":"GET","ret":"txt","paytoqs":"body","url":"","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":250,"y":60,"wires":[["042927082d1131a9"]]},{"id":"e227c1d5b36ed3d6","type":"function","z":"c3c63d388925a619","name":"List","func":"if (Array.isArray(msg.payload)) {\n msg.flows = msg.payload\n .filter(item => 'type' in item && item.type === 'tab')\n .map(item => ({\n label: item.label,\n id: item.id\n }));\n}\n\nreturn msg;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":510,"y":60,"wires":[["119bd9b989aca720"]]},{"id":"042927082d1131a9","type":"json","z":"c3c63d388925a619","name":"Convert","property":"payload","action":"obj","pretty":false,"x":380,"y":60,"wires":[["e227c1d5b36ed3d6"]]},{"id":"119bd9b989aca720","type":"array-loop","z":"c3c63d388925a619","name":"Flows","key":"flow","keyType":"msg","reset":true,"resetValue":"value-null","array":"flows","arrayType":"msg","x":650,"y":80,"wires":[[],["fd78953fd50dc364"]]},{"id":"cd342945bdc6ee11","type":"http request","z":"c3c63d388925a619","name":"Flow","method":"GET","ret":"txt","paytoqs":"ignore","url":"","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":770,"y":160,"wires":[["7940d1eca92f8dcf"]]},{"id":"fd78953fd50dc364","type":"function","z":"c3c63d388925a619","name":"Get","func":"msg.url = `http://eccentric-ethernet:1880/flow/${msg.flows[msg.flow].id}`\n\nflow.set('Path', `/tmp/${msg.flows[msg.flow].label}.json`)\n\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":650,"y":160,"wires":[["cd342945bdc6ee11"]]},{"id":"6bb549e9cbf6eae9","type":"file","z":"c3c63d388925a619","name":"Write","filename":"path","filenameType":"msg","appendNewline":true,"createDir":false,"overwriteFile":"true","encoding":"none","x":1010,"y":160,"wires":[["119bd9b989aca720"]]},{"id":"7940d1eca92f8dcf","type":"change","z":"c3c63d388925a619","name":"Set","rules":[{"t":"set","p":"path","pt":"msg","to":"Path","tot":"flow"}],"action":"","property":"","from":"","to":"","reg":false,"x":890,"y":160,"wires":[["6bb549e9cbf6eae9"]]},{"id":"600a0822be898286","type":"global-config","env":[],"modules":{"node-red-contrib-loop-processing":"0.5.1"}}]
Just need to make it pretty file format at some point.
What you're doing is exactly how I started ... until I wanted to organise changes to flows, i.e., track modifications. Support and coordinate usage of flows across multiple NR instances, coordinate deployment of flows to external NR instances etc.
But all this you might not yet need!
It all depends on your current use cases.
Right, exactly... Right now just needed a way to archive flows. But, should I need more, you gave me an excellent question to ask or idea to consider.
I think somewhere in the forum, is how to trigger the hosting browser to initiate flow download was documented. I think is what I recall seeing in the past...
https://discourse.nodered.org/t/click-button-and-file-sent-i-e-browser-downloading/85306
Since I added an input text box to set a persistent path for the export of flows to the NR host system, figure I might as well allow export to browser host file system as an option as well.
So the typical browser download is something like...
[HTTP In] ──► [HTTP Request] ──► [Change] ──► [HTTP Response]
However, I already have a flow variable that has the list of flow names, and ids, which I have used to export each flow to a file in the NR file system (see the discussion above and sample flow).
Now I like to read each flow file, and trigger the browser to download said file to the client system connected to NR, say via the dashboard, using a button to initiate an array loop. So I believe need in my array loop something more like this...
[Read In] ──► ??? ──► [Change] ──► [HTTP Response]
Clearly some 'HTTP' context is required. I get that the change node or response node needs headers set... where msg.file is the file name of the given flow, i.e "my flow.json"...
{
"Content-Disposition": "attachment; filename=${msg.file}",
"Content-Type": "application/json"
}
I don't want to have to trigger each download say via ui-template per flow, i.e. a button in ui-template for each flow file, using an [HTTP In] node. Just I just click a button, and browser downloads each flow from my array loop.
Web sites do this all the time, you have a list of files, click a button and all the files in the list ared downloaded.
Well, I managed to get something that works for a download button Using a dashboard 2.0 template node. But, it was 99% A.I. based...
<template>
<div class="d-flex w-100 justify-center align-center custom-btn-wrapper">
<v-btn
href="/down/load/flows.zip"
target="_blank"
color="primary"
variant="elevated"
class="w-100 msg-btn"
>
<v-icon start icon="mdi-tray-arrow-down"></v-icon>
Download
</v-btn>
</div>
</template>
<style>
.custom-btn-wrapper {
height: 100%;
}
.msg-btn {
text-transform: none !important;
letter-spacing: normal !important;
font-weight: 500 !important;
}
</style>
But I have an issue, how do I mimic how I can disable and enable dashboard buttons? I need this 'fake' button to be disabled and enable at the applicable times, i.e. msg.enabled=false, then msg.enabled=true. I am a complete CSS novice, no a clue how I might be able to do this?
Don't suppose I could create the href link in the template, as above, and just trigger it from a typical dashboard button?
Success! Well, at least to the level I need at this point. The flow below does an export of all flows, as individual files, to the provided directory, for example /tmp, given my Linux based NR environment, this is all server side, NR local of course. The download button generates a zip file of all the flow files (already generated), then invokes browser download sequence to client side file system. Still need to add logic to allow naming of the zip file, right now it defaults to flows.zip.
[{"id":"c3c63d388925a619","type":"tab","label":"Export Flows 0.0.1","disabled":false,"info":"","env":[]},{"id":"8e0aa9a17a268478","type":"inject","z":"c3c63d388925a619","name":"Export","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":150,"y":60,"wires":[["fa2be8942ee97549"]]},{"id":"1bb33d5da61ee17d","type":"http request","z":"c3c63d388925a619","name":"Request","method":"GET","ret":"txt","paytoqs":"body","url":"","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":680,"y":80,"wires":[["042927082d1131a9"]]},{"id":"e227c1d5b36ed3d6","type":"function","z":"c3c63d388925a619","name":"Flows","func":"//\nconst TYPE = 'type',\n TAB = 'tab',\n FLOWS = 'Flows';\n\n//\nvar theFlows = msg.payload\n .filter(theItem => TYPE in theItem && theItem.type === TAB)\n .map(theItem => (\n {\n Flow: theItem.label,\n Id: theItem.id,\n State: '?',\n Size: '?'\n }\n )\n);\n\n//\nflow.set(FLOWS, theFlows)\n\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":950,"y":80,"wires":[["2a91cbc921d98ccb"]]},{"id":"042927082d1131a9","type":"json","z":"c3c63d388925a619","name":"Convert","property":"payload","action":"obj","pretty":false,"x":820,"y":80,"wires":[["e227c1d5b36ed3d6"]]},{"id":"119bd9b989aca720","type":"array-loop","z":"c3c63d388925a619","name":"Flows","key":"flow","keyType":"msg","reset":true,"resetValue":"value-null","array":"Flows","arrayType":"flow","x":390,"y":260,"wires":[["bef10a3b126e58a4"],["fd78953fd50dc364"]]},{"id":"cd342945bdc6ee11","type":"http request","z":"c3c63d388925a619","name":"Request","method":"GET","ret":"txt","paytoqs":"ignore","url":"","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":680,"y":320,"wires":[["6bb549e9cbf6eae9"]]},{"id":"fd78953fd50dc364","type":"function","z":"c3c63d388925a619","name":"Flow","func":"//\nconst FLOWS = 'Flows',\n PERSISTENT = 'persistentOnly',\n PATH = 'Path';\n\n//\nconst HUNDRED = 100,\n ONE = 1;\n\n//\nvar thePath = flow.get(PATH, PERSISTENT),\n theFlows = flow.get(FLOWS);\n\n//\nmsg.url = `http://eccentric-ethernet:1880/flow/${theFlows[msg.flow].Id}`\nmsg.path = `${thePath}/${theFlows[msg.flow].Flow}.json`\nmsg.payload = (msg.flow + ONE) / theFlows.length * HUNDRED\n\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":530,"y":300,"wires":[["cd342945bdc6ee11","5aa122d35b6b1925"]]},{"id":"6bb549e9cbf6eae9","type":"file","z":"c3c63d388925a619","name":"Write","filename":"path","filenameType":"msg","appendNewline":false,"createDir":true,"overwriteFile":"true","encoding":"none","x":810,"y":320,"wires":[["7e892b79fa49bc80"]]},{"id":"29e688e483e43b3c","type":"link in","z":"c3c63d388925a619","name":"link in 272","links":["2a91cbc921d98ccb","8198ee3478daa245"],"x":305,"y":260,"wires":[["119bd9b989aca720"]]},{"id":"2a91cbc921d98ccb","type":"link out","z":"c3c63d388925a619","name":"link out 337","mode":"link","links":["29e688e483e43b3c","0fe18a70dd75fa12"],"x":1035,"y":80,"wires":[]},{"id":"d6b7a96ee3cf8a95","type":"link out","z":"c3c63d388925a619","name":"link out 340","mode":"link","links":["01eccbc8b6a1d026","be7ffcbaa8b4faf9"],"x":615,"y":220,"wires":[]},{"id":"f21ec889ee4bf171","type":"ui-table","z":"c3c63d388925a619","group":"731e0a61a8f23738","name":"Flows","label":"","order":6,"width":0,"height":0,"maxrows":0,"passthru":false,"autocols":true,"showSearch":false,"deselect":true,"selectionType":"none","columns":[],"mobileBreakpoint":"sm","mobileBreakpointType":"defaults","action":"replace","className":"","x":1310,"y":80,"wires":[[]]},{"id":"0fe18a70dd75fa12","type":"link in","z":"c3c63d388925a619","name":"link in 274","links":["2a91cbc921d98ccb","b391ab487f25ca77","a1c21b4a9976bda2","8198ee3478daa245"],"x":1105,"y":80,"wires":[["69ce329232c9724c"]]},{"id":"12253f6ccf169768","type":"ui-text","z":"c3c63d388925a619","group":"731e0a61a8f23738","order":5,"width":0,"height":0,"name":"Path","label":"Path","format":"{{msg.payload}}","layout":"row-left","style":false,"font":"","fontSize":16,"color":"#717171","wrapText":false,"className":"","value":"path","valueType":"msg","x":790,"y":160,"wires":[]},{"id":"bef10a3b126e58a4","type":"change","z":"c3c63d388925a619","name":"!Path","rules":[{"t":"set","p":"path","pt":"msg","to":"","tot":"str"},{"t":"delete","p":"payload","pt":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":530,"y":220,"wires":[["d6b7a96ee3cf8a95"]]},{"id":"390584ce5afa869a","type":"change","z":"c3c63d388925a619","name":"Location","rules":[{"t":"set","p":"url","pt":"msg","to":"http://eccentric-ethernet:1880/flows","tot":"str"},{"t":"set","p":"payload","pt":"msg","to":"[]","tot":"json"},{"t":"delete","p":"Flows","pt":"flow"},{"t":"set","p":"path","pt":"msg","to":"?","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":420,"y":80,"wires":[["b391ab487f25ca77"]]},{"id":"32090e696f2c6095","type":"link in","z":"c3c63d388925a619","name":"link in 276","links":["fa2be8942ee97549"],"x":325,"y":80,"wires":[["390584ce5afa869a"]]},{"id":"46c6baed2beb2c58","type":"ui-progress","z":"c3c63d388925a619","group":"731e0a61a8f23738","name":"Progress","label":"Export","order":3,"width":3,"height":0,"color":"","className":"","x":800,"y":240,"wires":[]},{"id":"fa2be8942ee97549","type":"link out","z":"c3c63d388925a619","name":"link out 342","mode":"link","links":["32090e696f2c6095"],"x":255,"y":80,"wires":[]},{"id":"d9557b776bc959c4","type":"link in","z":"c3c63d388925a619","name":"link in 278","links":["e00ef63703af6855"],"x":65,"y":100,"wires":[["ee66206b353bda21"]]},{"id":"b391ab487f25ca77","type":"link out","z":"c3c63d388925a619","name":"link out 343","mode":"link","links":["2c52912cb1bda1c2","0fe18a70dd75fa12","01eccbc8b6a1d026","9f8c3e25149c0d54"],"x":515,"y":80,"wires":[]},{"id":"2c52912cb1bda1c2","type":"link in","z":"c3c63d388925a619","name":"link in 279","links":["b391ab487f25ca77"],"x":585,"y":80,"wires":[["1bb33d5da61ee17d"]]},{"id":"8198ee3478daa245","type":"link out","z":"c3c63d388925a619","name":"link out 338","mode":"link","links":["29e688e483e43b3c","0fe18a70dd75fa12"],"x":1175,"y":320,"wires":[]},{"id":"e15077563bc135b3","type":"comment","z":"c3c63d388925a619","name":"Cosmetic To Allow UI To Render","info":"","x":1150,"y":280,"wires":[]},{"id":"7e0f571a1dd8e558","type":"delay","z":"c3c63d388925a619","name":"Delay(1s)","pauseType":"delay","timeout":"100","timeoutUnits":"milliseconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":1080,"y":320,"wires":[["8198ee3478daa245"]]},{"id":"5aa122d35b6b1925","type":"link out","z":"c3c63d388925a619","name":"link out 339","mode":"link","links":["01eccbc8b6a1d026"],"x":635,"y":280,"wires":[]},{"id":"01eccbc8b6a1d026","type":"link in","z":"c3c63d388925a619","name":"link in 275","links":["5aa122d35b6b1925","d6b7a96ee3cf8a95","b391ab487f25ca77","a1c21b4a9976bda2"],"x":685,"y":220,"wires":[["12253f6ccf169768","46c6baed2beb2c58"]]},{"id":"69ce329232c9724c","type":"change","z":"c3c63d388925a619","name":"Get","rules":[{"t":"set","p":"payload","pt":"msg","to":"Flows","tot":"flow","dc":true}],"action":"","property":"","from":"","to":"","reg":false,"x":1190,"y":80,"wires":[["f21ec889ee4bf171"]]},{"id":"ee66206b353bda21","type":"ui-button","z":"c3c63d388925a619","group":"731e0a61a8f23738","name":"Export","label":"Export","order":2,"width":1,"height":0,"emulateClick":false,"tooltip":"","color":"","bgcolor":"","className":"","icon":"file-export","iconPosition":"left","payload":"","payloadType":"str","topic":"topic","topicType":"msg","buttonColor":"","textColor":"","iconColor":"","enableClick":true,"enablePointerdown":false,"pointerdownPayload":"","pointerdownPayloadType":"str","enablePointerup":false,"pointerupPayload":"","pointerupPayloadType":"str","x":150,"y":100,"wires":[["fa2be8942ee97549"]]},{"id":"ae6bc9c0beb1bcaa","type":"inject","z":"c3c63d388925a619","name":"Reset","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":150,"y":140,"wires":[["7a335a54f2a2a08c"]]},{"id":"7a335a54f2a2a08c","type":"change","z":"c3c63d388925a619","name":"Set","rules":[{"t":"delete","p":"Flows","pt":"flow"},{"t":"set","p":"payload","pt":"msg","to":"0","tot":"num"},{"t":"set","p":"path","pt":"msg","to":"?","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":270,"y":140,"wires":[["a1c21b4a9976bda2"]]},{"id":"a1c21b4a9976bda2","type":"link out","z":"c3c63d388925a619","name":"link out 341","mode":"link","links":["0fe18a70dd75fa12","01eccbc8b6a1d026","be7ffcbaa8b4faf9"],"x":355,"y":140,"wires":[]},{"id":"7e892b79fa49bc80","type":"change","z":"c3c63d388925a619","name":"Update","rules":[{"t":"set","p":"Flows[msg.flow].State","pt":"flow","to":"statusCode","tot":"msg"},{"t":"set","p":"Flows[msg.flow].Size","pt":"flow","to":"headers.content-length","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":940,"y":320,"wires":[["7e0f571a1dd8e558"]]},{"id":"c191bf26112f9c10","type":"comment","z":"c3c63d388925a619","name":"Add Feature To Download Via Browser","info":"","x":190,"y":640,"wires":[]},{"id":"4a2fa1da499a99c3","type":"ui-text-input","z":"c3c63d388925a619","group":"731e0a61a8f23738","name":"Path","label":"Path","order":1,"width":0,"height":0,"topic":"topic","topicType":"msg","mode":"text","tooltip":"","delay":300,"passthru":false,"sendOnDelay":false,"sendOnBlur":true,"sendOnEnter":true,"className":"","clearable":true,"sendOnClear":true,"icon":"","iconPosition":"left","iconInnerPosition":"inside","x":510,"y":480,"wires":[["be22a10ae5ec97d7"]]},{"id":"6ad1dff433ee7733","type":"link in","z":"c3c63d388925a619","name":"link in 277","links":["e00ef63703af6855","741af49b574a6081"],"x":405,"y":500,"wires":[["4a2fa1da499a99c3"]]},{"id":"5dc18f08e432cbb8","type":"change","z":"c3c63d388925a619","name":"Get","rules":[{"t":"set","p":"payload","pt":"msg","to":"#:(persistentOnly)::Path","tot":"flow"}],"action":"","property":"","from":"","to":"","reg":false,"x":370,"y":460,"wires":[["4a2fa1da499a99c3"]]},{"id":"cb785ba93f344b98","type":"change","z":"c3c63d388925a619","name":"Enable","rules":[{"t":"set","p":"enabled","pt":"msg","to":"true","tot":"bool"},{"t":"delete","p":"payload","pt":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":160,"y":320,"wires":[["e00ef63703af6855"]]},{"id":"d00e65eb8c72c950","type":"change","z":"c3c63d388925a619","name":"Disable","rules":[{"t":"set","p":"enabled","pt":"msg","to":"false","tot":"bool"},{"t":"delete","p":"payload","pt":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":160,"y":360,"wires":[["e00ef63703af6855"]]},{"id":"be7ffcbaa8b4faf9","type":"link in","z":"c3c63d388925a619","name":"link in 281","links":["d6b7a96ee3cf8a95","a1c21b4a9976bda2","c9f140dff14e45bb"],"x":65,"y":320,"wires":[["cb785ba93f344b98"]]},{"id":"9f8c3e25149c0d54","type":"link in","z":"c3c63d388925a619","name":"link in 282","links":["b391ab487f25ca77","33f4f7db0f250c46"],"x":65,"y":360,"wires":[["d00e65eb8c72c950"]]},{"id":"e00ef63703af6855","type":"link out","z":"c3c63d388925a619","name":"link out 345","mode":"link","links":["6ad1dff433ee7733","d9557b776bc959c4","ce69b8f74c4bad3b"],"x":275,"y":340,"wires":[]},{"id":"be22a10ae5ec97d7","type":"function","z":"c3c63d388925a619","name":"Set","func":"//\nconst PERSISTENT = 'persistentOnly',\n SLASH = '/',\n EMPTY = '',\n PATH = 'Path';\n\n//\nvar thePath = msg.payload.replace(/\\/+/g, SLASH).replace(/\\/$/, EMPTY);\n\n//\nflow.set(PATH, thePath, PERSISTENT)\n\nmsg.payload = thePath\n\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":630,"y":480,"wires":[["741af49b574a6081"]]},{"id":"741af49b574a6081","type":"link out","z":"c3c63d388925a619","name":"link out 344","mode":"link","links":["6ad1dff433ee7733"],"x":715,"y":480,"wires":[]},{"id":"ee3fc66b8625c78c","type":"ui-event","z":"c3c63d388925a619","ui":"b9d115efa0ea05a2","name":"Dashboard","x":100,"y":440,"wires":[["3348dc1d60160715"]]},{"id":"3348dc1d60160715","type":"switch","z":"c3c63d388925a619","name":"Page?","property":"payload","propertyType":"msg","rules":[{"t":"cont","v":"Casitas","vt":"str"}],"checkall":"true","repair":false,"outputs":1,"x":230,"y":440,"wires":[["5dc18f08e432cbb8"]]},{"id":"3cf5f6a7ce831a8b","type":"inject","z":"c3c63d388925a619","name":"Down Load","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":170,"y":700,"wires":[["33f4f7db0f250c46"]]},{"id":"33f4f7db0f250c46","type":"link out","z":"c3c63d388925a619","name":"link out 346","mode":"link","links":["305ce6e6bdc1b579","9f8c3e25149c0d54"],"x":295,"y":720,"wires":[]},{"id":"60ef89c4b74d34b1","type":"array-loop","z":"c3c63d388925a619","name":"Flows","key":"flow","keyType":"msg","reset":true,"resetValue":"value-null","array":"Flows","arrayType":"flow","x":450,"y":720,"wires":[["0353fae280fa8b05"],["88e7ca5d313020fc"]]},{"id":"305ce6e6bdc1b579","type":"link in","z":"c3c63d388925a619","name":"link in 283","links":["33f4f7db0f250c46","6b40eb584c4dbdd3"],"x":365,"y":720,"wires":[["60ef89c4b74d34b1"]]},{"id":"6b40eb584c4dbdd3","type":"link out","z":"c3c63d388925a619","name":"link out 348","mode":"link","links":["305ce6e6bdc1b579"],"x":775,"y":800,"wires":[]},{"id":"fde7175ec9d5d63c","type":"zip","z":"c3c63d388925a619","name":"Compress","mode":"compress","filename":"","compressionlevel":6,"outasstring":false,"x":730,"y":700,"wires":[["8f4ca63ab983b396"]]},{"id":"a4b35559b895698b","type":"file in","z":"c3c63d388925a619","name":"Read","filename":"path","filenameType":"msg","format":"","chunk":false,"sendError":false,"encoding":"none","allProps":false,"x":570,"y":800,"wires":[["2acdaad311e1d764"]]},{"id":"d385f71665efd3fc","type":"function","z":"c3c63d388925a619","name":"File","func":"//\nconst FLOWS = 'Flows',\n PERSISTENT = 'persistentOnly',\n PATH = 'Path';\n\n//\nvar thePath = flow.get(PATH, PERSISTENT),\n theFlows = flow.get(FLOWS);\n\n//\nmsg.file = `${theFlows[msg.flow].Flow}.json`\nmsg.path = `${thePath}/${msg.file}`\n\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":450,"y":800,"wires":[["a4b35559b895698b"]]},{"id":"d10cbf5caee815ac","type":"link in","z":"c3c63d388925a619","name":"link in 284","links":["88e7ca5d313020fc"],"x":365,"y":800,"wires":[["d385f71665efd3fc"]]},{"id":"88e7ca5d313020fc","type":"link out","z":"c3c63d388925a619","name":"link out 349","mode":"link","links":["d10cbf5caee815ac"],"x":555,"y":740,"wires":[]},{"id":"2acdaad311e1d764","type":"change","z":"c3c63d388925a619","name":"File","rules":[{"t":"set","p":"Files[msg.flow].filename","pt":"flow","to":"file","tot":"msg"},{"t":"set","p":"Files[msg.flow].payload","pt":"flow","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":690,"y":800,"wires":[["6b40eb584c4dbdd3"]]},{"id":"0353fae280fa8b05","type":"change","z":"c3c63d388925a619","name":"Files","rules":[{"t":"set","p":"payload","pt":"msg","to":"Files","tot":"flow"}],"action":"","property":"","from":"","to":"","reg":false,"x":590,"y":700,"wires":[["fde7175ec9d5d63c"]]},{"id":"c9f140dff14e45bb","type":"link out","z":"c3c63d388925a619","name":"link out 347","mode":"link","links":["be7ffcbaa8b4faf9"],"x":1095,"y":680,"wires":[]},{"id":"8f4ca63ab983b396","type":"file","z":"c3c63d388925a619","name":"Write","filename":"/tmp/flows.zip","filenameType":"str","appendNewline":false,"createDir":true,"overwriteFile":"true","encoding":"none","x":870,"y":700,"wires":[["814a94058e864575"]]},{"id":"7878b06865679a04","type":"http in","z":"c3c63d388925a619","name":"In","url":"/down/load/flows.zip","method":"get","upload":false,"skipBodyParsing":false,"swaggerDoc":"","x":410,"y":880,"wires":[["9f0f7528257229ab"]]},{"id":"5a82e12592187fd2","type":"http response","z":"c3c63d388925a619","name":"Response","statusCode":"","headers":{"Content-Type":"application/json","Content-Disposition":"attachment; filename=flows.zip"},"x":660,"y":880,"wires":[]},{"id":"9f0f7528257229ab","type":"file in","z":"c3c63d388925a619","name":"Read","filename":"/tmp/flows.zip","filenameType":"str","format":"","chunk":false,"sendError":false,"encoding":"none","allProps":false,"x":530,"y":880,"wires":[["5a82e12592187fd2"]]},{"id":"4196fbb5bc236e31","type":"ui-button","z":"c3c63d388925a619","group":"731e0a61a8f23738","name":"Download","label":"Download","order":4,"width":1,"height":0,"emulateClick":false,"tooltip":"","color":"","bgcolor":"","className":"","icon":"","iconPosition":"left","payload":"","payloadType":"date","topic":"topic","topicType":"msg","buttonColor":"","textColor":"","iconColor":"","enableClick":true,"enablePointerdown":false,"pointerdownPayload":"","pointerdownPayloadType":"str","enablePointerup":false,"pointerupPayload":"","pointerupPayloadType":"str","x":180,"y":740,"wires":[["33f4f7db0f250c46"]]},{"id":"ce69b8f74c4bad3b","type":"link in","z":"c3c63d388925a619","name":"link in 285","links":["e00ef63703af6855"],"x":85,"y":740,"wires":[["4196fbb5bc236e31"]]},{"id":"79ed12bcfbfabe0e","type":"ui-control","z":"c3c63d388925a619","name":"Control","ui":"b9d115efa0ea05a2","events":"all","x":1140,"y":720,"wires":[[]]},{"id":"814a94058e864575","type":"change","z":"c3c63d388925a619","name":"Set","rules":[{"t":"set","p":"payload","pt":"msg","to":"{\"url\":\"http:/down/load/flows.zip\"}","tot":"json"}],"action":"","property":"","from":"","to":"","reg":false,"x":990,"y":700,"wires":[["79ed12bcfbfabe0e","c9f140dff14e45bb"]]},{"id":"731e0a61a8f23738","type":"ui-group","name":"Flows","page":"a129af50497c821b","width":4,"height":"2","order":1,"showTitle":true,"className":"","visible":"true","disabled":"false","groupType":"default"},{"id":"b9d115efa0ea05a2","type":"ui-base","name":"Dashboard","path":"/dashboard","appIcon":"","includeClientData":true,"acceptsClientConfig":["ui-notification","ui-control"],"showPathInSidebar":false,"headerContent":"page","navigationStyle":"default","titleBarStyle":"default","showReconnectNotification":true,"notificationDisplayTime":1,"showDisconnectNotification":true,"allowInstall":false},{"id":"a129af50497c821b","type":"ui-page","name":"Dachshund Digital","ui":"b9d115efa0ea05a2","path":"/Casitas","icon":"home","layout":"grid","theme":"eae0f4a4353a3ec9","breakpoints":[{"name":"Default","px":"0","cols":"3"},{"name":"Tablet","px":"576","cols":"6"},{"name":"Small Desktop","px":"768","cols":"9"},{"name":"Desktop","px":"1024","cols":"12"}],"order":2,"className":"","visible":true,"disabled":false},{"id":"eae0f4a4353a3ec9","type":"ui-theme","name":"Dark","colors":{"surface":"#008080","primary":"#008080","bgPage":"#000000","groupBg":"#000000","groupOutline":"#cccccc"},"sizes":{"density":"default","pagePadding":"12px","groupGap":"12px","groupBorderRadius":"4px","widgetGap":"12px"}},{"id":"7624dda3f39f81f1","type":"global-config","env":[],"modules":{"node-red-contrib-loop-processing":"0.5.1","@flowfuse/node-red-dashboard":"1.30.2","node-red-contrib-zip":"1.1.0"}}]
I am sure someone will see ways to improve it? Chime in of course.
I have done this many times, for different reasons. This is a cut-down example of what I did; you can use this to update a git repository, database, etc.
There is an admin API that you OAuth to and update or read flows
I have grouped this into two sections: the API Token and a group that fetches all flows, and then each individual flow.
Please note: I needed to edit this and remove some things that may have exposed security risks, but feel free to play and clean up. There are many OAuth nodes that have been contributed, and you're more than welcome to use those; I just wanted to show a native flow.
There is an inject in the Token Group that is just for testing the auth and returning a token.
Just inject on the Get Flows group, and it will fetch the token and dump out your flows. Again, note I'm using JSONata to get just the tab information for the flow id's, then going back and getting each flow.
Currently it's just dumping the flow for debugging, but you could download it, write it to a file, or write it to a database, import a contributed GIT node; the sky's the limit.
I use this process for promoting flows between dev -> uat -> prod. We disabled the export option.
You will need to set the admin password in the flow environment "NR_ADMIN_PASS"
The token scope is "read" only, so you cannot do any damage
[{"id":"08e48cf83d61f0f6","type":"group","z":"a38e4d6fae5d0768","name":"Fetch NodeRed API Token","style":{"stroke":"#ff0000","fill":"#ffbfbf","label":true,"color":"#000000"},"nodes":["b24b4fe9ea11f3fd","ae0535bcd1fad7be","5fd89efc9d3e816c","35a696b939e075d5","7c92e16ae54560fa","8f53f2671ed8124d","742ffbab132d5788","a460571b772ce9f6","c77d424b2f245898","346a40085192a7b7","f6bd1504cd15ec7c","683c5fda6918b5b9","3779aee1db9973c6","071477b0289a025a","4698445359050357","8803e96621e0a6b7","52e0e84942d6de0e","740acffbcbaeea71"],"x":74,"y":479,"w":1162,"h":282},{"id":"b24b4fe9ea11f3fd","type":"change","z":"a38e4d6fae5d0768","g":"08e48cf83d61f0f6","name":"Set Payload","rules":[{"t":"set","p":"payload.username","pt":"msg","to":"admin","tot":"str"},{"t":"set","p":"payload.password","pt":"msg","to":"NR_ADMIN_PASS","tot":"env"},{"t":"set","p":"payload.client_id","pt":"msg","to":"node-red-admin","tot":"str"},{"t":"set","p":"payload.grant_type","pt":"msg","to":"password","tot":"str"},{"t":"set","p":"payload.scope","pt":"msg","to":"read","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":330,"y":680,"wires":[["ae0535bcd1fad7be"]]},{"id":"ae0535bcd1fad7be","type":"http request","z":"a38e4d6fae5d0768","g":"08e48cf83d61f0f6","name":"","method":"POST","ret":"obj","paytoqs":"ignore","url":"http://127.0.0.1:1880/auth/token","tls":"610d97c4.ffa868","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":490,"y":680,"wires":[["5fd89efc9d3e816c","742ffbab132d5788"]]},{"id":"5fd89efc9d3e816c","type":"function","z":"a38e4d6fae5d0768","g":"08e48cf83d61f0f6","name":"Set Token.","func":"let expires_in = parseInt(msg.payload.expires_in) - 600;\nlet t = new Date();\nt.setSeconds(t.getSeconds() + expires_in);\nlet timeStamp = t.getTime();\nlet nodeRedAuth = {};\nnodeRedAuth.expires = timeStamp;\nnodeRedAuth.access_token = msg.payload.access_token;\nflow.set('nodeRedAuth', nodeRedAuth);\nmsg.access_token = msg.payload.access_token;\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":650,"y":680,"wires":[["683c5fda6918b5b9"]]},{"id":"35a696b939e075d5","type":"function","z":"a38e4d6fae5d0768","g":"08e48cf83d61f0f6","name":"Get nodeRedAuth","func":"let nodeRedAuth = flow.get('nodeRedAuth');\n\nvar t = new Date();\nlet timeStamp = t.getTime();\nmsg.needRefresh = false;\nif (timeStamp >= nodeRedAuth.expires) msg.needRefresh = true;\nmsg.access_token = nodeRedAuth.access_token;\n\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":630,"y":554,"wires":[["8f53f2671ed8124d"]]},{"id":"7c92e16ae54560fa","type":"switch","z":"a38e4d6fae5d0768","g":"08e48cf83d61f0f6","name":"Check if we have nodeRedAuth","property":"nodeRedAuth","propertyType":"flow","rules":[{"t":"nnull"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":370,"y":560,"wires":[["35a696b939e075d5"],["346a40085192a7b7"]]},{"id":"8f53f2671ed8124d","type":"switch","z":"a38e4d6fae5d0768","g":"08e48cf83d61f0f6","name":"Check if we need new token","property":"needRefresh","propertyType":"msg","rules":[{"t":"false"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":861,"y":554,"wires":[["740acffbcbaeea71"],["f6bd1504cd15ec7c"]]},{"id":"742ffbab132d5788","type":"debug","z":"a38e4d6fae5d0768","g":"08e48cf83d61f0f6","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":630,"y":720,"wires":[]},{"id":"a460571b772ce9f6","type":"inject","z":"a38e4d6fae5d0768","g":"08e48cf83d61f0f6","name":"","props":[],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":170,"y":680,"wires":[["b24b4fe9ea11f3fd"]]},{"id":"c77d424b2f245898","type":"link in","z":"a38e4d6fae5d0768","g":"08e48cf83d61f0f6","name":"link in 184","links":["346a40085192a7b7","f6bd1504cd15ec7c"],"x":204,"y":644,"wires":[["b24b4fe9ea11f3fd"]]},{"id":"346a40085192a7b7","type":"link out","z":"a38e4d6fae5d0768","g":"08e48cf83d61f0f6","name":"link out 262","mode":"link","links":["c77d424b2f245898"],"x":556,"y":592,"wires":[]},{"id":"f6bd1504cd15ec7c","type":"link out","z":"a38e4d6fae5d0768","g":"08e48cf83d61f0f6","name":"link out 263","mode":"link","links":["c77d424b2f245898"],"x":1035,"y":580,"wires":[]},{"id":"683c5fda6918b5b9","type":"link out","z":"a38e4d6fae5d0768","g":"08e48cf83d61f0f6","name":"link out 264","mode":"link","links":["3779aee1db9973c6"],"x":755,"y":680,"wires":[]},{"id":"3779aee1db9973c6","type":"link in","z":"a38e4d6fae5d0768","g":"08e48cf83d61f0f6","name":"link in 185","links":["683c5fda6918b5b9"],"x":945,"y":520,"wires":[["740acffbcbaeea71"]]},{"id":"071477b0289a025a","type":"comment","z":"a38e4d6fae5d0768","g":"08e48cf83d61f0f6","name":"Admin APi Auth Token","info":"","x":360,"y":640,"wires":[]},{"id":"4698445359050357","type":"comment","z":"a38e4d6fae5d0768","g":"08e48cf83d61f0f6","name":"Admin API Fetch Flows(tabs)","info":"","x":400,"y":520,"wires":[]},{"id":"8803e96621e0a6b7","type":"link in","z":"a38e4d6fae5d0768","g":"08e48cf83d61f0f6","name":"Get NodeRed Auth","links":[],"x":206,"y":574,"wires":[["7c92e16ae54560fa"]]},{"id":"52e0e84942d6de0e","type":"link out","z":"a38e4d6fae5d0768","g":"08e48cf83d61f0f6","name":"link out 1","mode":"return","links":[],"x":1195,"y":540,"wires":[]},{"id":"740acffbcbaeea71","type":"function","z":"a38e4d6fae5d0768","g":"08e48cf83d61f0f6","name":"Set Headers","func":"let nodeRedAuth = flow.get('nodeRedAuth');\nmsg.access_token = nodeRedAuth.access_token;\n\n//node.warn(msg.access_token);\n\nmsg.headers = {};\nmsg.headers['content-type'] = 'application/json';\nmsg.headers['Authorization'] = \"Bearer \" + msg.access_token;\n\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":1090,"y":540,"wires":[["52e0e84942d6de0e"]]},{"id":"610d97c4.ffa868","type":"tls-config","name":"No Cert Verify","cert":"","key":"","ca":"","certname":"","keyname":"","caname":"","servername":"","verifyservercert":false,"alpnprotocol":""},{"id":"974d6b465c413a93","type":"group","z":"a38e4d6fae5d0768","name":"Get Flows","style":{"stroke":"#92d04f","fill":"#e3f3d3","label":true,"color":"#000000"},"nodes":["91d183d544881590","44c37584e98b4960","aa2b4e99a95499cc","dcca2e5225000e14","7fab1f074241ab2e","876f5d3e80d4bedb","8ec900ee1b2d3adc","2a43c207feb16c73"],"x":74,"y":799,"w":1272,"h":82},{"id":"91d183d544881590","type":"inject","z":"a38e4d6fae5d0768","g":"974d6b465c413a93","name":"","props":[],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":170,"y":840,"wires":[["44c37584e98b4960"]]},{"id":"44c37584e98b4960","type":"link call","z":"a38e4d6fae5d0768","g":"974d6b465c413a93","name":"NR-Auth","links":["8803e96621e0a6b7"],"linkType":"static","timeout":"30","x":300,"y":840,"wires":[["aa2b4e99a95499cc"]]},{"id":"aa2b4e99a95499cc","type":"http request","z":"a38e4d6fae5d0768","g":"974d6b465c413a93","name":"","method":"GET","ret":"obj","paytoqs":"ignore","url":"http://127.0.0.1:1880/flows","tls":"610d97c4.ffa868","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":450,"y":840,"wires":[["dcca2e5225000e14"]]},{"id":"dcca2e5225000e14","type":"change","z":"a38e4d6fae5d0768","g":"974d6b465c413a93","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"$.payload[type = \"tab\"]\t","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":620,"y":840,"wires":[["7fab1f074241ab2e"]]},{"id":"7fab1f074241ab2e","type":"split","z":"a38e4d6fae5d0768","g":"974d6b465c413a93","name":"","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","property":"payload","x":790,"y":840,"wires":[["8ec900ee1b2d3adc"]]},{"id":"876f5d3e80d4bedb","type":"http request","z":"a38e4d6fae5d0768","g":"974d6b465c413a93","name":"","method":"GET","ret":"obj","paytoqs":"ignore","url":"","tls":"610d97c4.ffa868","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":1090,"y":840,"wires":[["2a43c207feb16c73"]]},{"id":"8ec900ee1b2d3adc","type":"function","z":"a38e4d6fae5d0768","g":"974d6b465c413a93","name":"Get Flow ID","func":"let nodeRedAuth = flow.get('nodeRedAuth');\nmsg.access_token = nodeRedAuth.access_token;\n\n//node.warn(msg.access_token);\n\nmsg.headers = {};\nmsg.headers['content-type'] = 'application/json';\nmsg.headers['Authorization'] = \"Bearer \" + msg.access_token;\n\n\nmsg.url = \"http://127.0.0.1:1880/flow/\" + msg.payload.id;\nnode.warn(msg.url)\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":930,"y":840,"wires":[["876f5d3e80d4bedb"]]},{"id":"2a43c207feb16c73","type":"debug","z":"a38e4d6fae5d0768","g":"974d6b465c413a93","name":"debug 2","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":1240,"y":840,"wires":[]}]
Let me know if you need me to explain anything
Harry
This is really interesting, taking a basic NR flow archival flow, and 'clouding' it so to speak, archiving to some type of back end solution, of course.