Low code Cisco DevOps automation with Node-RED

Jeremy Worden
automate builders
Published in
14 min readNov 6, 2023

Today we’ll be using Node-RED as a DevOps tool to automate Cisco Collaboration workflows. Node-RED is an easy to use application that allows users to create functionality by wiring together flows. These flows can then be easily shared as JSON files.

Getting Started

This boilerplate consists of the Node-RED image based on Node 18. It will also install the following Cisco Collaboration packages via NPM.

Additionally, it will also install the FlowFuse Dashboard 2.0 that we can use to interact with our flows.

To get started lets clone the boilerplate from GitHub and use Docker to get the application up and running:

git clone https://github.com/sieteunoseis/cisco-vos-nodered-bolierplate.git
cd cisco-vos-nodered-bolierplate
docker-compose up -d

Your application should now be up and running on port 1880! Navigate to http://localhost:1880 to access application.

Now that our boilerplate is up and running. Let’s take a look at some examples.

Get Random SIP Trunk via AXL

Here is a fully working example flow for retrieving the configuration of a random SIP trunk. This example shows how to build a flow that will display the data on a dashboard after retrieving.

Node-RED flow to retrieve a random SIP Trunk from Cisco CUCM application

You can import this flow with the following JSON:

[{"id":"60644299e58c7c53","type":"subflow","name":"Set Global Variables","info":"","category":"","in":[{"x":40,"y":80,"wires":[{"id":"f08e510bbf870d27"}]}],"out":[{"x":400,"y":80,"wires":[{"id":"f08e510bbf870d27","port":0}]}],"env":[],"meta":{},"color":"#DDAA99"},{"id":"f08e510bbf870d27","type":"change","z":"60644299e58c7c53","name":"Set Global Variables","rules":[{"t":"set","p":"hostname","pt":"global","to":"hq-cucm-pub.abc.inc","tot":"str"},{"t":"set","p":"username","pt":"global","to":"administrator","tot":"str"},{"t":"set","p":"password","pt":"global","to":"ciscopsdt","tot":"str"},{"t":"set","p":"version","pt":"global","to":"14.0","tot":"str"},{"t":"set","p":"ciscoAxlOpts","pt":"global","to":"{\"clean\":true,\"removeAttributes\":false,\"dataContainerIdentifierTails\":\"_data\"}","tot":"json"},{"t":"set","p":"debug","pt":"global","to":"false","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":220,"y":80,"wires":[[]]},{"id":"9d09ad44a1d8cfef","type":"tab","label":"AXL — Random SIP Trunk","disabled":false,"info":"# Introduction\n\nThis flow demonstrates how to use nodes to interface with Cisco AXL API. The final result is diplaying a table on the dashboard of a random SIP trunk.\n\n# Set Global Variables Group\n\nSet global variables needed for flow to run. Note: this flow is set to auto run once after being deployed.\n\n# Return All Operations Group\n\nUses AXL to return an array of all the operations users can use via AXL. Output is viewable in the Debug messages tab. Click next to \"Press here ->\" on injection to start flow.\n\n# Return Operations with Filter Group\n\nUses AXL to return an array of all the operations users can use via AXL, filtered by keyword. Useful if you wanted to find all AXL operations that had the word \"phone\" in it as an example. Output is viewable in the Debug messages tab. Click next to \"Press here ->\" on injection to start flow.\n\n# Get Random SIP Trunk Group\n\nUses AXL to retrieve a random SIP trunk and display results in a table to the user. Click next to \"Press here ->\" on injection to start flow, or via form on UI dashboard.","env":[]},{"id":"624832a2fb087436","type":"group","z":"9d09ad44a1d8cfef","name":"Set Global Variables Group","style":{"label":true},"nodes":["af4227575775487d","1a80d16add27b309","5428f3977c5f9c86","234bc8b4609e370f","e9a74c8b987834e0"],"x":34,"y":59,"w":972,"h":122},{"id":"5e131529b1525be2","type":"group","z":"9d09ad44a1d8cfef","name":"Return All Operations Group","style":{"label":true},"nodes":["475b89a28f9e7ab5","853c94ff3ad03a7e","bfc9ccc1e9466a23","6292f890b1b24eee","591982ccb0f527f9"],"x":34,"y":199,"w":972,"h":142},{"id":"7c8a44c41684705d","type":"group","z":"9d09ad44a1d8cfef","name":"Return Operations with Filter Group","style":{"label":true},"nodes":["cdba16f310ac15c6","5a13e4f32538d506","f3e225601e0e0429","b9c462316925e74d","c68528eeda140778"],"x":34,"y":379,"w":972,"h":142},{"id":"6f101b74d7ad4085","type":"group","z":"9d09ad44a1d8cfef","name":"Get Random SIP Trunk Group","style":{"label":true},"nodes":["d99317627aa41903","a12b7c8869f6e1fa","bbf77229aa5b40ea","2416470f2b89eec9","61a3698fccb814ea","223adf1aa5c328ef","78adbeb053e64068","66f6b14997e6ae9b","629c91408aecb192","82f325dc80e52adb","2273fa315ba06f4b","da0505e42946b287","925ab0b059e02556","398d0586af379d3f","ff18cb83d5b4074b","b6018c440e223a4d","ce390508bbf6881d","6532eaf6f583f04e","4ac06dd4e7168b2a","b3db52093d05b479","448a5099cd3b8c37","4185eecfe7bad6eb","02231b158874ce78","7ea40055f154c6a3","8ee9a7af249a3343","686e38a7dae027dc","235c66d492bfe1f2","bc4eae98dc9cdc6e","a669ecc3b20364c8"],"x":34,"y":559,"w":1252,"h":902},{"id":"475b89a28f9e7ab5","type":"function","z":"9d09ad44a1d8cfef","g":"5e131529b1525be2","name":"Return Operations","func":"try {\n let service = new ciscoAxl(global.get(\"hostname\"), global.get(\"username\"), global.get(\"password\"), global.get(\"version\"));\n\n var msg;\n var operations = await service.returnOperations();\n \n msg = {\n payload: operations\n };\n\n return msg;\n} catch (error) {\n let errMsg = null\n errMsg = JSON.stringify(error);\n throw new Error(errMsg)\n}","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[{"var":"ciscoAxl","module":"cisco-axl"}],"x":610,"y":300,"wires":[["853c94ff3ad03a7e"]]},{"id":"853c94ff3ad03a7e","type":"debug","z":"9d09ad44a1d8cfef","g":"5e131529b1525be2","name":"Debug payload","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":880,"y":300,"wires":[]},{"id":"af4227575775487d","type":"inject","z":"9d09ad44a1d8cfef","g":"624832a2fb087436","name":"Set Variables","props":[{"p":"payload"}],"repeat":"","crontab":"","once":true,"onceDelay":0.1,"topic":"","payload":"{\"variables\":\"set\",\"flow\":\"Get Random SIP Trunk\"}","payloadType":"json","x":350,"y":140,"wires":[["234bc8b4609e370f"]],"outputLabels":["Payload"],"info":"# Introduction\n\nWhere we set the initial payload. This will be the where results are stored as it flows thru nodes."},{"id":"1a80d16add27b309","type":"comment","z":"9d09ad44a1d8cfef","g":"624832a2fb087436","name":"Set to Auto Run ->","info":"First node will initalize flow.\n\nMake sure your AXL settings are correct in the ‘Set Global Variables’ node.\n\nThis will auto run every time you \"Deploy\" the flow.","x":150,"y":140,"wires":[]},{"id":"bfc9ccc1e9466a23","type":"comment","z":"9d09ad44a1d8cfef","g":"5e131529b1525be2","name":"Return All Operations","info":"# Return All Operations\n\nThis flow will return all the operations available via AXL on this CUCM cluster.\n\nResults returned in array.","x":360,"y":240,"wires":[]},{"id":"cdba16f310ac15c6","type":"inject","z":"9d09ad44a1d8cfef","g":"7c8a44c41684705d","name":"Set Filter Keyword","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"returnOperations","payload":"{\"filter\":\"siptrunk\"}","payloadType":"json","x":370,"y":480,"wires":[["5a13e4f32538d506"]],"outputLabels":["Payload"],"info":"# Introduction\n\nInject node to start flow. Set filter keyword here."},{"id":"5a13e4f32538d506","type":"function","z":"9d09ad44a1d8cfef","g":"7c8a44c41684705d","name":"Return Operations (Filter)","func":"try {\n let service = new ciscoAxl(global.get(\"hostname\"), global.get(\"username\"), global.get(\"password\"), global.get(\"version\"));\n\n var newMsg;\n var operations = await service.returnOperations(msg.payload.filter);\n \n newMsg = {\n payload: operations,\n topic: msg.topic\n };\n\n return newMsg;\n} catch (error) {\n let errMsg = null\n errMsg = JSON.stringify(error);\n throw new Error(errMsg)\n}","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[{"var":"ciscoAxl","module":"cisco-axl"}],"x":630,"y":480,"wires":[["f3e225601e0e0429"]]},{"id":"f3e225601e0e0429","type":"debug","z":"9d09ad44a1d8cfef","g":"7c8a44c41684705d","name":"Debug payload","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":880,"y":480,"wires":[]},{"id":"b9c462316925e74d","type":"comment","z":"9d09ad44a1d8cfef","g":"7c8a44c41684705d","name":"Return Operations With \"siptrunk\" Filter","info":"This flow will get all the operations available via AXL on this CUCM cluster.\n\nResults returned in array, filtered by keyword.","x":410,"y":420,"wires":[]},{"id":"d99317627aa41903","type":"function","z":"9d09ad44a1d8cfef","g":"6f101b74d7ad4085","name":"Get Operations Tags","func":"try {\n let service = new ciscoAxl(global.get(\"hostname\"), global.get(\"username\"), global.get(\"password\"), global.get(\"version\"));\n\n var msg;\n var operation = \"getSipTrunk\";\n var tags = await service.getOperationTags(operation);\n\n msg = {\n payload: tags,\n topic: ‘getSipTrunk’\n };\n\n return msg;\n} catch (error) {\n let errMsg = null\n errMsg = JSON.stringify(error);\n throw new Error(errMsg)\n}","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[{"var":"ciscoAxl","module":"cisco-axl"}],"x":300,"y":1220,"wires":[["da0505e42946b287","a669ecc3b20364c8"]]},{"id":"a12b7c8869f6e1fa","type":"comment","z":"9d09ad44a1d8cfef","g":"6f101b74d7ad4085","name":"Get all tags needed for operation: ‘getSipTrunk’","info":"This flow will get all the operations available via AXL on this CUCM cluster.\n\nResults returned in array, filtered by keyword.","x":300,"y":1160,"wires":[]},{"id":"bbf77229aa5b40ea","type":"inject","z":"9d09ad44a1d8cfef","g":"6f101b74d7ad4085","name":"Set operation","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"operation\":\"listSipTrunk\"}","payloadType":"json","x":370,"y":660,"wires":[["2416470f2b89eec9"]],"outputLabels":["Payload"],"info":"# Introduction\n\nUse injection node to start flow. Here you can specify the payload that will be send to the next node. Useful for passing settings to next node."},{"id":"2416470f2b89eec9","type":"function","z":"9d09ad44a1d8cfef","g":"6f101b74d7ad4085","name":"Get Operations Tags","func":"try {\n let service = new ciscoAxl(global.get(\"hostname\"), global.get(\"username\"), global.get(\"password\"), global.get(\"version\"));\n\n var newMsg;\n var operation = msg.payload.operation;\n var tags = await service.getOperationTags(operation);\n\n newMsg = {\n payload: tags,\n topic: operation\n };\n\n return newMsg;\n} catch (error) {\n let errMsg = null\n errMsg = JSON.stringify(error);\n throw new Error(errMsg)\n}","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[{"var":"ciscoAxl","module":"cisco-axl"}],"x":700,"y":660,"wires":[["223adf1aa5c328ef","a669ecc3b20364c8"]]},{"id":"61a3698fccb814ea","type":"comment","z":"9d09ad44a1d8cfef","g":"6f101b74d7ad4085","name":"Get All Tags needed for operation: ‘listSipTrunk’","info":"This flow will get all the operations available via AXL on this CUCM cluster.\n\nResults returned in array, filtered by keyword.","x":420,"y":600,"wires":[]},{"id":"223adf1aa5c328ef","type":"function","z":"9d09ad44a1d8cfef","g":"6f101b74d7ad4085","name":"Return Results from ‘listSipTrunk’","func":"try {\n let service = new ciscoAxl(global.get(\"hostname\"), global.get(\"username\"), global.get(\"password\"), global.get(\"version\"));\n var newMsg;\n var operation = msg.topic;\n\n msg.payload.searchCriteria.name = \"%%\";\n msg.payload.searchCriteria.description = \"%%\";\n msg.payload.searchCriteria.callingSearchSpaceName = \"%%\";\n msg.payload.searchCriteria.devicePoolName = \"%%\";\n\n var results = await service.executeOperation(operation, msg.payload, global.get(\"ciscoAxlOpts\"));\n\n newMsg = {\n payload: results,\n topic: ‘listSipTrunkData’\n };\n\n return newMsg;\n} catch (error) {\n let errMsg = null\n errMsg = JSON.stringify(error);\n throw new Error(errMsg)\n}","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[{"var":"ciscoAxl","module":"cisco-axl"}],"x":280,"y":980,"wires":[["ff18cb83d5b4074b","a669ecc3b20364c8"]]},{"id":"78adbeb053e64068","type":"comment","z":"9d09ad44a1d8cfef","g":"6f101b74d7ad4085","name":"Call ‘listSipTrunk’ operation","info":"This function will get all the operations available via AXL on this CUCM cluster.\n\nResults returned in array, filtered by keyword.","x":300,"y":920,"wires":[]},{"id":"6292f890b1b24eee","type":"inject","z":"9d09ad44a1d8cfef","g":"5e131529b1525be2","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{}","payloadType":"json","x":330,"y":300,"wires":[["475b89a28f9e7ab5"]],"outputLabels":["Payload"],"info":"We set the initial payload to {}. This will be the where results are stored as it flows thru nodes."},{"id":"5428f3977c5f9c86","type":"debug","z":"9d09ad44a1d8cfef","g":"624832a2fb087436","name":"Debug payload","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":880,"y":140,"wires":[]},{"id":"66f6b14997e6ae9b","type":"function","z":"9d09ad44a1d8cfef","g":"6f101b74d7ad4085","name":"Select random item from ‘listSipTrunk’","func":"var newMsg;\nconst randomItem = msg.payload.sipTrunk[Math.floor(Math.random() * msg.payload.sipTrunk.length)];\n\nnewMsg = {\n payload: { name: randomItem.name },\n topic : ‘randomSipTrunk’\n};\n\nreturn newMsg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":350,"y":1100,"wires":[["da0505e42946b287","a669ecc3b20364c8"]]},{"id":"629c91408aecb192","type":"debug","z":"9d09ad44a1d8cfef","g":"6f101b74d7ad4085","name":"Debug payload","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1140,"y":860,"wires":[]},{"id":"c68528eeda140778","type":"comment","z":"9d09ad44a1d8cfef","g":"7c8a44c41684705d","name":"Press here ->","info":"# Manual Execution\n\nUse injection to manually start flow. Useful for troubleshooting flow(s).","x":130,"y":480,"wires":[]},{"id":"591982ccb0f527f9","type":"comment","z":"9d09ad44a1d8cfef","g":"5e131529b1525be2","name":"Press here ->","info":"","x":130,"y":300,"wires":[]},{"id":"82f325dc80e52adb","type":"comment","z":"9d09ad44a1d8cfef","g":"6f101b74d7ad4085","name":"Press here ->","info":"# Manual Execution\n\nUse this injection to manually start flow. Useful for troubleshooting flow(s).","x":170,"y":660,"wires":[]},{"id":"2273fa315ba06f4b","type":"link in","z":"9d09ad44a1d8cfef","g":"6f101b74d7ad4085","name":"getSipTrunk Tags Link In","links":["ff18cb83d5b4074b","574297447e9723cd"],"x":95,"y":1220,"wires":[["d99317627aa41903"]]},{"id":"da0505e42946b287","type":"join","z":"9d09ad44a1d8cfef","g":"6f101b74d7ad4085","name":"Join msg parts into one. Msg Parts:","mode":"custom","build":"object","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":false,"timeout":"","count":"2","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"num","reduceFixup":"","x":690,"y":1220,"wires":[["925ab0b059e02556"]],"info":"Joining outputs from the last two flows. We will need data from both for the final flow function.\n"},{"id":"925ab0b059e02556","type":"function","z":"9d09ad44a1d8cfef","g":"6f101b74d7ad4085","name":"Get ‘getSipTrunk’ results","func":"try {\n let service = new ciscoAxl(global.get(\"hostname\"), global.get(\"username\"), global.get(\"password\"), global.get(\"version\"));\n var newMsg;\n var operation = \"getSipTrunk\";\n // Let’s update the returned JSON with the name of the trunk we are trying to copy\n msg.payload.getSipTrunk.name = msg.payload.randomSipTrunk.name\n\n var results = await service.executeOperation(operation, msg.payload.getSipTrunk, global.get(\"ciscoAxlOpts\"));\n\n newMsg = {\n payload: results\n };\n\n return newMsg;\n} catch (error) {\n let errMsg = null\n errMsg = JSON.stringify(error);\n throw new Error(errMsg)\n}","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[{"var":"ciscoAxl","module":"cisco-axl"}],"x":1030,"y":1220,"wires":[["02231b158874ce78","a669ecc3b20364c8"]]},{"id":"398d0586af379d3f","type":"comment","z":"9d09ad44a1d8cfef","g":"6f101b74d7ad4085","name":"Get results for random SIP Trunk","info":"This function will use ‘getSipTrunk’ to return the values of the SIP trunk, using the tags in the first step.\n\n","x":1030,"y":1160,"wires":[]},{"id":"234bc8b4609e370f","type":"subflow:60644299e58c7c53","z":"9d09ad44a1d8cfef","g":"624832a2fb087436","name":"Set Global Variables","x":600,"y":140,"wires":[["5428f3977c5f9c86"]]},{"id":"ff18cb83d5b4074b","type":"link out","z":"9d09ad44a1d8cfef","g":"6f101b74d7ad4085","name":"listSipTrunk Link Out","mode":"link","links":["2273fa315ba06f4b","b6018c440e223a4d"],"x":545,"y":1000,"wires":[]},{"id":"b6018c440e223a4d","type":"link in","z":"9d09ad44a1d8cfef","g":"6f101b74d7ad4085","name":"randomSipTrunk Link In","links":["ff18cb83d5b4074b","574297447e9723cd"],"x":95,"y":1100,"wires":[["66f6b14997e6ae9b"]]},{"id":"ce390508bbf6881d","type":"comment","z":"9d09ad44a1d8cfef","g":"6f101b74d7ad4085","name":"Select Random Item","info":"","x":350,"y":1040,"wires":[]},{"id":"6532eaf6f583f04e","type":"catch","z":"9d09ad44a1d8cfef","g":"6f101b74d7ad4085","name":"Catch Error","scope":null,"uncaught":false,"x":290,"y":1420,"wires":[["4ac06dd4e7168b2a"]]},{"id":"4ac06dd4e7168b2a","type":"function","z":"9d09ad44a1d8cfef","g":"6f101b74d7ad4085","name":"Error Function","func":"\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":640,"y":1420,"wires":[["b3db52093d05b479"]]},{"id":"b3db52093d05b479","type":"debug","z":"9d09ad44a1d8cfef","g":"6f101b74d7ad4085","name":"Debug error","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"error","targetType":"msg","statusVal":"","statusType":"auto","x":1170,"y":1420,"wires":[]},{"id":"448a5099cd3b8c37","type":"comment","z":"9d09ad44a1d8cfef","g":"6f101b74d7ad4085","name":"Error Handling","info":"","x":290,"y":1380,"wires":[]},{"id":"4185eecfe7bad6eb","type":"ui-table","z":"9d09ad44a1d8cfef","g":"6f101b74d7ad4085","group":"9b417ddf901f7c08","name":"Dashboard Table","label":"text","order":0,"width":0,"height":0,"maxrows":"10","autocols":true,"columns":[],"x":630,"y":1360,"wires":[]},{"id":"02231b158874ce78","type":"function","z":"9d09ad44a1d8cfef","g":"6f101b74d7ad4085","name":"Flatten JSON to Table","func":"var jsonObj = msg.payload\n\nconst flatTable = Object.entries(jsonObj.sipTrunk).map(([key, value]) => ({\n key,\n value\n}));\n\nvar newMsg = {\n payload: flatTable\n}\n\nreturn newMsg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":300,"y":1320,"wires":[["4185eecfe7bad6eb"]]},{"id":"7ea40055f154c6a3","type":"comment","z":"9d09ad44a1d8cfef","g":"6f101b74d7ad4085","name":"View @ http://<node-red ip add>:<port>/dashboard/axl","info":"","x":680,"y":1320,"wires":[]},{"id":"8ee9a7af249a3343","type":"ui-button","z":"9d09ad44a1d8cfef","g":"6f101b74d7ad4085","group":"9b417ddf901f7c08","name":"","label":"Get Random SIP Trunk","order":0,"width":0,"height":0,"passthru":false,"tooltip":"","color":"","bgcolor":"","className":"","icon":"","payload":"{\"operation\":\"listSipTrunk\"}","payloadType":"json","topic":"topic","topicType":"msg","x":380,"y":780,"wires":[["2416470f2b89eec9"]]},{"id":"686e38a7dae027dc","type":"comment","z":"9d09ad44a1d8cfef","g":"6f101b74d7ad4085","name":"Dashboard Form ->","info":"","x":150,"y":780,"wires":[]},{"id":"e9a74c8b987834e0","type":"comment","z":"9d09ad44a1d8cfef","g":"624832a2fb087436","name":"CHANGE ME.","info":"# Edit Subflow\n\nUpdate credentials to set global variables.\n\nNote: This should only need to be done once for all flows in project that use global variables.","x":610,"y":100,"wires":[]},{"id":"235c66d492bfe1f2","type":"comment","z":"9d09ad44a1d8cfef","g":"6f101b74d7ad4085","name":"View @ http://<node-red ip add>:<port>/dashboard/axl","info":"","x":280,"y":720,"wires":[]},{"id":"bc4eae98dc9cdc6e","type":"comment","z":"9d09ad44a1d8cfef","g":"6f101b74d7ad4085","name":"Pipe output to debug console","info":"","x":1060,"y":800,"wires":[]},{"id":"a669ecc3b20364c8","type":"switch","z":"9d09ad44a1d8cfef","g":"6f101b74d7ad4085","name":"Debug Variable Check","property":"debug","propertyType":"global","rules":[{"t":"eq","v":"true","vt":"str"}],"checkall":"true","repair":false,"outputs":1,"x":920,"y":860,"wires":[["629c91408aecb192"]]},{"id":"9b417ddf901f7c08","type":"ui-group","name":"Get Random SIP Trunk","page":"0a87b32a472e0d54","width":"6","height":"1","order":-1,"disp":true},{"id":"0a87b32a472e0d54","type":"ui-page","name":"AXL Dashboard","ui":"20412f87db1b0cd7","path":"/axl","layout":"grid","theme":"e0714ab1e4e245cf","order":-1},{"id":"20412f87db1b0cd7","type":"ui-base","name":"Dashboard","path":"/dashboard"},{"id":"e0714ab1e4e245cf","type":"ui-theme","name":"Normal","colors":{"surface":"#ffffff","primary":"#0094ce","bgPage":"#eeeeee","groupBg":"#ffffff","groupOutline":"#cccccc"}]

Once imported you can either activate the flow via the inject node or via the dashboard.

Note: Start with editing the “Set Global Variables” subflow to update your credentials.

To view the dashboard navigate to http://localhost:1880/dashboard/axl

Dashboard view of Get Random SIP Trunk

Copy SIP Trunk via AXL

This example shows how to use Node-RED to copy a SIP trunk via AXL. This example is similar to the “Super Copy” functionality within Cisco Unified Communications Manager.

Node-RED flow to retrieve a copy a SIP Trunk from Cisco CUCM application

This flow uses a form on the dashboard to collect information from the user.

Form is accessible at: http://localhost:1880/dashboard/axl.

User is presented with a notification once trunk has been successfully copied.

Import this flow with the following JSON:

[{"id":"60644299e58c7c53","type":"subflow","name":"Set Global Variables","info":"","category":"","in":[{"x":40,"y":80,"wires":[{"id":"f08e510bbf870d27"}]}],"out":[{"x":400,"y":80,"wires":[{"id":"f08e510bbf870d27","port":0}]}],"env":[],"meta":{},"color":"#DDAA99"},{"id":"f08e510bbf870d27","type":"change","z":"60644299e58c7c53","name":"Set Global Variables","rules":[{"t":"set","p":"hostname","pt":"global","to":"hq-cucm-pub.abc.inc","tot":"str"},{"t":"set","p":"username","pt":"global","to":"administrator","tot":"str"},{"t":"set","p":"password","pt":"global","to":"ciscopsdt","tot":"str"},{"t":"set","p":"version","pt":"global","to":"14.0","tot":"str"},{"t":"set","p":"ciscoAxlOpts","pt":"global","to":"{\"clean\":true,\"removeAttributes\":false,\"dataContainerIdentifierTails\":\"_data\"}","tot":"json"},{"t":"set","p":"debug","pt":"global","to":"false","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":220,"y":80,"wires":[[]]},{"id":"ff5e1ce63f8cbba1","type":"tab","label":"AXL - Copy SIP Trunk","disabled":false,"info":"# Introduction\n\nThis flow demonstrates how to use nodes to interface with Cisco AXL API. The final result is copying a SIP Trunk.\n\n# Set Global Variables Group\n\nSet global variables needed for flow to run. Note: this flow is set to auto run once after being deployed.\n\n# Copy SIP Trunk Group\n\nUses AXL to copy a SIP trunk. Similar to \"Super Copy\" functionality built into Cisco Unified Communications Manager.\n\nFlow is initiated via a form on dashboard. Navigate [here](http://localhost:1880/dashboard/copytrunk) to start. Users are notified via notification on dashboard once flow has been completed.","env":[]},{"id":"60b804dd122799e9","type":"group","z":"ff5e1ce63f8cbba1","name":"Set Global Variables Group","style":{"label":true},"nodes":["c1fa5a5216cba7f7","55a39c63725d45dc","d4e762f637ca1630","27a2ad46df60add4","f811012c01e87e27"],"x":34,"y":19,"w":1132,"h":122},{"id":"5108fa6c2d7d2f80","type":"group","z":"ff5e1ce63f8cbba1","name":"Copy SIP Trunk Group","style":{"label":true},"nodes":["b7327fcf71f6446f","5f7e702ed0f90bb0","c83c0efce3894bd4","52367d191d2aa22f","857e7c8f775d1d3b","891594f086d8ae35","58f1622c5663a714","bafbc2ba717038a1","c9d2a29b590e65dd","cadd4b8f923d6285","df21bc3c26056007","83819789723600cf","8256f2740a544c70","65127ff930599159","4cc74405d2b5d9f0","419fd37fc6bfd128"],"x":34,"y":159,"w":1132,"h":642},{"id":"b7327fcf71f6446f","type":"function","z":"ff5e1ce63f8cbba1","g":"5108fa6c2d7d2f80","name":"Get Operation 'getSipTrunk' Tags","func":"try {\n let service = new ciscoAxl(global.get(\"hostname\"), global.get(\"username\"), global.get(\"password\"), global.get(\"version\"));\n\n var msg;\n var operation = \"getSipTrunk\";\n var tags = await service.getOperationTags(operation);\n \n msg = {\n payload: tags\n };\n\n return msg;\n} catch (error) {\n let errMsg = null\n errMsg = JSON.stringify(error);\n throw new Error(errMsg)\n}","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[{"var":"ciscoAxl","module":"cisco-axl"}],"x":380,"y":380,"wires":[["891594f086d8ae35","419fd37fc6bfd128"]]},{"id":"c1fa5a5216cba7f7","type":"inject","z":"ff5e1ce63f8cbba1","g":"60b804dd122799e9","name":"Set Variables","props":[{"p":"payload"}],"repeat":"","crontab":"","once":true,"onceDelay":0.1,"topic":"","payload":"{\"variables\":\"set\",\"flow\":\"Copy SIP Trunk\"}","payloadType":"json","x":350,"y":100,"wires":[["55a39c63725d45dc"]],"outputLabels":["Payload"],"info":"# Introduction\n\nWhere we set the initial payload. This will be the where results are stored as it flows thru nodes."},{"id":"5f7e702ed0f90bb0","type":"comment","z":"ff5e1ce63f8cbba1","g":"5108fa6c2d7d2f80","name":"Error Handling","info":"","x":310,"y":700,"wires":[]},{"id":"c83c0efce3894bd4","type":"catch","z":"ff5e1ce63f8cbba1","g":"5108fa6c2d7d2f80","name":"Catch Error","scope":null,"uncaught":false,"x":310,"y":760,"wires":[["52367d191d2aa22f"]]},{"id":"52367d191d2aa22f","type":"function","z":"ff5e1ce63f8cbba1","g":"5108fa6c2d7d2f80","name":"Error Function","func":"\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":660,"y":760,"wires":[["857e7c8f775d1d3b"]]},{"id":"857e7c8f775d1d3b","type":"debug","z":"ff5e1ce63f8cbba1","g":"5108fa6c2d7d2f80","name":"Debug error","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"error","targetType":"msg","statusVal":"","statusType":"auto","x":1030,"y":760,"wires":[]},{"id":"891594f086d8ae35","type":"function","z":"ff5e1ce63f8cbba1","g":"5108fa6c2d7d2f80","name":"Get 'getSipTrunk' results","func":"try {\n let service = new ciscoAxl(global.get(\"hostname\"), global.get(\"username\"), global.get(\"password\"), global.get(\"version\"));\n var newMsg;\n var operation = \"getSipTrunk\";\n // Let's update the returned JSON with the name of the trunk we are trying to copy\n msg.payload.name = flow.get(\"sipTrunkToCopy\");\n\n var results = await service.executeOperation(operation, msg.payload, global.get(\"ciscoAxlOpts\"));\n\n msg = {\n payload: results\n };\n\n return msg;\n} catch (error) {\n let errMsg = null\n errMsg = JSON.stringify(error);\n throw new Error(errMsg)\n}","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[{"var":"ciscoAxl","module":"cisco-axl"}],"x":350,"y":520,"wires":[["c9d2a29b590e65dd","419fd37fc6bfd128"]]},{"id":"58f1622c5663a714","type":"comment","z":"ff5e1ce63f8cbba1","g":"5108fa6c2d7d2f80","name":"Get tags for 'getSipTrunk'","info":"This function will get the tags needed for make a valid 'getSipTrunk' call via AXL.\n","x":350,"y":320,"wires":[]},{"id":"bafbc2ba717038a1","type":"comment","z":"ff5e1ce63f8cbba1","g":"5108fa6c2d7d2f80","name":"Get Results","info":"This function will use 'getSipTrunk' to return the values of the SIP trunk, using the tags in the first step.","x":310,"y":440,"wires":[]},{"id":"c9d2a29b590e65dd","type":"function","z":"ff5e1ce63f8cbba1","g":"5108fa6c2d7d2f80","name":"Get 'addSipTrunk' results","func":"try {\n let service = new ciscoAxl(global.get(\"hostname\"), global.get(\"username\"), global.get(\"password\"), global.get(\"version\"));\n var newMsg;\n var operation = \"addSipTrunk\";\n // Let's update the returned JSON with the name of the trunk we are trying to copy\n msg.payload.sipTrunk.name = flow.get(\"newSipTrunkName\");\n msg.payload.sipTrunk.description = flow.get(\"newSipTrunkDescription\");\n msg.payload.sipTrunk.destinations.destination[0].addressIpv4 = flow.get(\"newSipTrunkIP\");\n msg.payload.sipTrunk.destinations.destination[0].port = flow.get(\"newSipTrunkPort\");\n\n // Confidential Access Mode\tis returned from CUCM via AXL as undefined if not set on the phone we are copying. \n // We either need to set it to '' or delete it complete. Since we're not using this feature, let's delete it from our JSON.\n delete msg.payload.sipTrunk.confidentialAccess;\n\n var newTrunk = await service.executeOperation(operation, msg.payload, global.get(\"ciscoAxlOpts\"));\n\n msg = {\n payload: \"New trunk successfully copied: <br>\" + newTrunk\n };\n\n return msg;\n} catch (error) {\n let errMsg = null\n errMsg = JSON.stringify(error);\n throw new Error(errMsg)\n}","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[{"var":"ciscoAxl","module":"cisco-axl"}],"x":350,"y":640,"wires":[["4cc74405d2b5d9f0","419fd37fc6bfd128"]]},{"id":"cadd4b8f923d6285","type":"debug","z":"ff5e1ce63f8cbba1","g":"5108fa6c2d7d2f80","name":"Debug payload","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1040,"y":300,"wires":[]},{"id":"df21bc3c26056007","type":"comment","z":"ff5e1ce63f8cbba1","g":"5108fa6c2d7d2f80","name":"Add SIP Trunk","info":"This function will use 'addSipTrunk'.","x":310,"y":580,"wires":[]},{"id":"55a39c63725d45dc","type":"subflow:60644299e58c7c53","z":"ff5e1ce63f8cbba1","g":"60b804dd122799e9","name":"Set Global Variables","x":700,"y":100,"wires":[["d4e762f637ca1630"]]},{"id":"d4e762f637ca1630","type":"debug","z":"ff5e1ce63f8cbba1","g":"60b804dd122799e9","name":"Debug payload","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1040,"y":100,"wires":[]},{"id":"83819789723600cf","type":"ui-form","z":"ff5e1ce63f8cbba1","g":"5108fa6c2d7d2f80","name":"Copy SIP Trunk Form","group":"6173c25158384f18","label":"","order":0,"width":0,"height":0,"options":[{"label":"Trunk to Copy","key":"sipTrunkToCopy","type":"text","required":true,"rows":null},{"label":"Name of new trunk","key":"newSipTrunkName","type":"text","required":true,"rows":null},{"label":"Description of new trunk","key":"newSipTrunkDescription","type":"multiline","required":false,"rows":3},{"label":"IP Address","key":"newSipTrunkIP","type":"text","required":true,"rows":null},{"label":"Port","key":"newSipTrunkPort","type":"number","required":true,"rows":null}],"formValue":{"sipTrunkToCopy":"","newSipTrunkName":"","newSipTrunkDescription":"","newSipTrunkIP":"","newSipTrunkPort":""},"payload":"","submit":"submit","cancel":"clear","resetOnSubmit":false,"topic":"topic","topicType":"msg","splitLayout":false,"className":"","x":160,"y":260,"wires":[["8256f2740a544c70"]]},{"id":"8256f2740a544c70","type":"change","z":"ff5e1ce63f8cbba1","g":"5108fa6c2d7d2f80","name":"Set Flow Variables","rules":[{"t":"set","p":"sipTrunkToCopy","pt":"flow","to":"payload.sipTrunkToCopy","tot":"msg"},{"t":"set","p":"newSipTrunkName","pt":"flow","to":"payload.newSipTrunkName","tot":"msg"},{"t":"set","p":"newSipTrunkDescription","pt":"flow","to":"payload.newSipTrunkDescription","tot":"msg"},{"t":"set","p":"newSipTrunkIP","pt":"flow","to":"payload.newSipTrunkIP","tot":"msg"},{"t":"set","p":"newSipTrunkPort","pt":"flow","to":"payload.newSipTrunkPort","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":410,"y":260,"wires":[["b7327fcf71f6446f"]],"info":"# Introduction\n\nUse this node to set variables that are unique to this flow within a project. Variables can then be accessed in other nodes with the following:\n\n``\nflow.get(\"variable_name\");\n``"},{"id":"65127ff930599159","type":"comment","z":"ff5e1ce63f8cbba1","g":"5108fa6c2d7d2f80","name":"View @ http://<node-red ip add>:<port>/dashboard/copytrunk","info":"","x":420,"y":200,"wires":[]},{"id":"4cc74405d2b5d9f0","type":"ui-notification","z":"ff5e1ce63f8cbba1","g":"5108fa6c2d7d2f80","ui":"20412f87db1b0cd7","position":"top right","displayTime":"3","showCountdown":true,"outputs":0,"allowDismiss":true,"dismissText":"Close","raw":true,"className":"","name":"Successfully Notification","x":1030,"y":640,"wires":[]},{"id":"27a2ad46df60add4","type":"comment","z":"ff5e1ce63f8cbba1","g":"60b804dd122799e9","name":"CHANGE ME.","info":"# Edit Subflow\n\nUpdate credentials to set global variables.\n\nNote: This should only need to be done once for all flows in project that use global variables.","x":690,"y":60,"wires":[]},{"id":"419fd37fc6bfd128","type":"switch","z":"ff5e1ce63f8cbba1","g":"5108fa6c2d7d2f80","name":"Debug Variable Check","property":"debug","propertyType":"global","rules":[{"t":"eq","v":"true","vt":"str"}],"checkall":"true","repair":false,"outputs":1,"x":820,"y":300,"wires":[["cadd4b8f923d6285"]]},{"id":"f811012c01e87e27","type":"comment","z":"ff5e1ce63f8cbba1","g":"60b804dd122799e9","name":"Set to Auto Run ->","info":"First node will initalize flow.\n\nMake sure your AXL settings are correct in the 'Set Global Variables' node.\n\nThis will auto run every time you \"Deploy\" the flow.","x":150,"y":100,"wires":[]},{"id":"6173c25158384f18","type":"ui-group","name":"Copy SIP Trunk","page":"0a87b32a472e0d54","width":"6","height":"1","order":-1,"disp":true},{"id":"20412f87db1b0cd7","type":"ui-base","name":"Dashboard","path":"/dashboard"},{"id":"0a87b32a472e0d54","type":"ui-page","name":"AXL Dashboard","ui":"20412f87db1b0cd7","path":"/axl","layout":"grid","theme":"e0714ab1e4e245cf","order":-1},{"id":"e0714ab1e4e245cf","type":"ui-theme","name":"Normal","colors":{"surface":"#ffffff","primary":"#0094ce","bgPage":"#eeeeee","groupBg":"#ffffff","groupOutline":"#cccccc"}}]

Display live SIP Trunks stats via Perfmon

This flow will collect Perfmon stats for all SIP Trunks and display on a dashboard. This flow could also be edited to write values to an InfluxDB as well.

Node-RED dashboard displaying Perfmon Stats

Import this flow with the following JSON:

[{"id":"60644299e58c7c53","type":"subflow","name":"Set Global Variables","info":"","category":"","in":[{"x":40,"y":80,"wires":[{"id":"f08e510bbf870d27"}]}],"out":[{"x":400,"y":80,"wires":[{"id":"f08e510bbf870d27","port":0}]}],"env":[],"meta":{},"color":"#DDAA99"},{"id":"f08e510bbf870d27","type":"change","z":"60644299e58c7c53","name":"Set Global Variables","rules":[{"t":"set","p":"hostname","pt":"global","to":"hq-cucm-pub.abc.inc","tot":"str"},{"t":"set","p":"username","pt":"global","to":"administrator","tot":"str"},{"t":"set","p":"password","pt":"global","to":"ciscopsdt","tot":"str"},{"t":"set","p":"version","pt":"global","to":"14.0","tot":"str"},{"t":"set","p":"ciscoAxlOpts","pt":"global","to":"{\"clean\":true,\"removeAttributes\":false,\"dataContainerIdentifierTails\":\"_data\"}","tot":"json"},{"t":"set","p":"debug","pt":"global","to":"false","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":220,"y":80,"wires":[[]]},{"id":"4b368d486490923a","type":"tab","label":"Perfmon - SIP Trunk","disabled":false,"info":"# Introduction\n\nThis flow demonstrates how to use nodes to interface with Cisco Perfmon API. The final result displays a chart on dashboard.\n\n# Set Global & Flow Variables Group\n\nSet global/flow variables needed for flow to run. Note: this flow is set to auto run once after being deployed.\n\n# Get SIP Calls Group\n\nFlow to retrieve call data via Cisco Perfmon API. Results are displayed on chart on dashboard. Alternatively, data could be sent to an InfluxDB and graphed via Grafana. This group can be edited to update the frequency of the data refresh. Note: this flow is set to auto run once after being deployed and refresh every x seconds.","env":[]},{"id":"365baa3159166665","type":"group","z":"4b368d486490923a","name":"Set Global & Flow Variables Group","style":{"label":true},"nodes":["d1306cb64714b4ba","4667fdedfa1ac92e","9fe651ec73617c64","cbd7e2203ffd5425","15fe497467acba03"],"x":14,"y":59,"w":1152,"h":122},{"id":"b09909183a90841b","type":"group","z":"4b368d486490923a","name":"Get SIP Calls Group","style":{"label":true},"nodes":["262f79bafe3bdabb","eca8df56a53e6dd5","def29667e8fd221a","db8de876d6fb69ca","405afd0b76e038e6","43a89debfdbef1a1","bcc7e6fa41e5612c","b93d86a621db2fc8","951ba13975f8a47b","3ddaa1632f29f96e","b5e415a39903f51c","15506ac7d7da7cc4","6e066a06997f9673","a2cf0254a2c9731d","86c8baa1ebc4ec3e","bbb68292d6dfb96c","98cd0eb26ab5c2f3"],"x":14,"y":199,"w":1152,"h":502},{"id":"d1306cb64714b4ba","type":"inject","z":"4b368d486490923a","g":"365baa3159166665","name":"Set Variables","props":[{"p":"payload"}],"repeat":"","crontab":"","once":true,"onceDelay":0.1,"topic":"","payload":"{\"variables\":\"set\",\"flow\":\"SIP Perfmon\"}","payloadType":"json","x":330,"y":140,"wires":[["4667fdedfa1ac92e"]],"outputLabels":["Payload"],"info":"# Introduction\n\nWhere we set the initial payload. This will be the where results are stored as it flows thru nodes."},{"id":"4667fdedfa1ac92e","type":"subflow:60644299e58c7c53","z":"4b368d486490923a","g":"365baa3159166665","name":"Set Global Variables","x":620,"y":140,"wires":[["9fe651ec73617c64"]]},{"id":"9fe651ec73617c64","type":"debug","z":"4b368d486490923a","g":"365baa3159166665","name":"Debug payload","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1040,"y":140,"wires":[]},{"id":"262f79bafe3bdabb","type":"function","z":"4b368d486490923a","g":"b09909183a90841b","name":"Get 'Cisco SIP' Perfmon Data","func":"try {\n let service = new ciscoPerfmon(global.get(\"hostname\"), global.get(\"username\"), global.get(\"password\"));\n\n var msg;\n var counterObj = {\n host: \"hq-cucm-pub\",\n };\n\n var results = await service.collectCounterData(counterObj.host, \"Cisco SIP\")\n\n // let's get the timestamp for the time this data was processed \n let d = new Date()\n \n msg = {\n payload: results,\n timestamp: d\n };\n\n return msg;\n} catch (error) {\n let errMsg = null\n errMsg = JSON.stringify(error);\n throw new Error(errMsg)\n}","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[{"var":"ciscoPerfmon","module":"cisco-perfmon"}],"x":750,"y":240,"wires":[["b93d86a621db2fc8","98cd0eb26ab5c2f3"]]},{"id":"eca8df56a53e6dd5","type":"catch","z":"4b368d486490923a","g":"b09909183a90841b","name":"Catch Error","scope":null,"uncaught":false,"x":290,"y":660,"wires":[["def29667e8fd221a"]]},{"id":"def29667e8fd221a","type":"function","z":"4b368d486490923a","g":"b09909183a90841b","name":"Error Function","func":"\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":640,"y":660,"wires":[["db8de876d6fb69ca"]]},{"id":"db8de876d6fb69ca","type":"debug","z":"4b368d486490923a","g":"b09909183a90841b","name":"Debug error","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"error","targetType":"msg","statusVal":"","statusType":"auto","x":1030,"y":660,"wires":[]},{"id":"405afd0b76e038e6","type":"inject","z":"4b368d486490923a","g":"b09909183a90841b","name":"","props":[{"p":"payload"}],"repeat":"10","crontab":"","once":true,"onceDelay":0.1,"topic":"","payload":"{}","payloadType":"str","x":510,"y":240,"wires":[["262f79bafe3bdabb"]]},{"id":"43a89debfdbef1a1","type":"comment","z":"4b368d486490923a","g":"b09909183a90841b","name":"Set to Auto Run & Repeat Every 10 Seconds ->","info":"","x":220,"y":240,"wires":[]},{"id":"bcc7e6fa41e5612c","type":"debug","z":"4b368d486490923a","g":"b09909183a90841b","name":"Debug payload","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1020,"y":360,"wires":[]},{"id":"b93d86a621db2fc8","type":"split","z":"4b368d486490923a","g":"b09909183a90841b","name":"Split results","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":210,"y":380,"wires":[["15506ac7d7da7cc4"]]},{"id":"951ba13975f8a47b","type":"comment","z":"4b368d486490923a","g":"b09909183a90841b","name":"Error Handling","info":"","x":290,"y":620,"wires":[]},{"id":"3ddaa1632f29f96e","type":"comment","z":"4b368d486490923a","g":"b09909183a90841b","name":"View @ http://<node-red ip add>:<port>/dashboard/perfmon","info":"","x":920,"y":420,"wires":[]},{"id":"b5e415a39903f51c","type":"ui-chart","z":"4b368d486490923a","g":"b09909183a90841b","group":"c9c9de97d03e7c4c","name":"","label":"Calls Active","order":9007199254740991,"chartType":"line","xAxisType":"time","removeOlder":"15","removeOlderUnit":"60","removeOlderPoints":"","colors":["#1f77b4","#aec7e8","#ff7f0e","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5"],"width":"0","height":"0","className":"","x":1050,"y":460,"wires":[[]]},{"id":"15506ac7d7da7cc4","type":"function","z":"4b368d486490923a","g":"b09909183a90841b","name":"Format for Dashboard Chart","func":"var msg = {\n \"topic\": msg.payload.instance + \"(\" + msg.payload.counter + \")\",\n \"payload\": {\n x: msg.timestamp,\n y: parseInt(msg.payload.value)\n },\n \"counter\": msg.payload.counter\n}\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":520,"y":480,"wires":[["a2cf0254a2c9731d"]]},{"id":"cbd7e2203ffd5425","type":"comment","z":"4b368d486490923a","g":"365baa3159166665","name":"CHANGE ME.","info":"# Edit Subflow\n\nUpdate credentials to set global variables.","x":630,"y":100,"wires":[]},{"id":"6e066a06997f9673","type":"ui-chart","z":"4b368d486490923a","g":"b09909183a90841b","group":"c9c9de97d03e7c4c","name":"","label":"Calls Attempted","order":9007199254740991,"chartType":"line","xAxisType":"time","removeOlder":"15","removeOlderUnit":"60","removeOlderPoints":"","colors":["#1f77b4","#aec7e8","#ff7f0e","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5"],"width":"0","height":"0","className":"","x":1040,"y":500,"wires":[[]]},{"id":"a2cf0254a2c9731d","type":"switch","z":"4b368d486490923a","g":"b09909183a90841b","name":"Switch by Call Type","property":"counter","propertyType":"msg","rules":[{"t":"eq","v":"CallsActive","vt":"str"},{"t":"eq","v":"CallsAttempted","vt":"str"},{"t":"eq","v":"CallsCompleted","vt":"str"},{"t":"eq","v":"CallsInProgress","vt":"str"}],"checkall":"true","repair":false,"outputs":4,"x":770,"y":480,"wires":[["b5e415a39903f51c"],["6e066a06997f9673"],["86c8baa1ebc4ec3e"],["bbb68292d6dfb96c"]]},{"id":"86c8baa1ebc4ec3e","type":"ui-chart","z":"4b368d486490923a","g":"b09909183a90841b","group":"c9c9de97d03e7c4c","name":"","label":"Calls Completed","order":9007199254740991,"chartType":"line","xAxisType":"time","removeOlder":"15","removeOlderUnit":"60","removeOlderPoints":"","colors":["#1f77b4","#aec7e8","#ff7f0e","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5"],"width":"0","height":"0","className":"","x":1030,"y":540,"wires":[[]]},{"id":"bbb68292d6dfb96c","type":"ui-chart","z":"4b368d486490923a","g":"b09909183a90841b","group":"c9c9de97d03e7c4c","name":"","label":"Calls In Progress","order":9007199254740991,"chartType":"line","xAxisType":"time","removeOlder":"15","removeOlderUnit":"60","removeOlderPoints":"","colors":["#1f77b4","#aec7e8","#ff7f0e","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5"],"width":"0","height":"0","className":"","x":1030,"y":580,"wires":[[]]},{"id":"98cd0eb26ab5c2f3","type":"switch","z":"4b368d486490923a","g":"b09909183a90841b","name":"Debug Variable Check","property":"debug","propertyType":"global","rules":[{"t":"eq","v":"true","vt":"str"}],"checkall":"true","repair":false,"outputs":1,"x":800,"y":360,"wires":[["bcc7e6fa41e5612c"]]},{"id":"15fe497467acba03","type":"comment","z":"4b368d486490923a","g":"365baa3159166665","name":"Set to Auto Run ->","info":"First node will initalize flow.\n\nMake sure your AXL settings are correct in the 'Set Global Variables' node.\n\nThis will auto run every time you \"Deploy\" the flow.","x":130,"y":140,"wires":[]},{"id":"c9c9de97d03e7c4c","type":"ui-group","name":"SIP Perfmon","page":"5788592495c79015","width":"6","height":"1","order":-1,"disp":true},{"id":"5788592495c79015","type":"ui-page","name":"Perfmon Dashboard","ui":"20412f87db1b0cd7","path":"/perfmon","layout":"grid","theme":"e0714ab1e4e245cf","order":-1},{"id":"20412f87db1b0cd7","type":"ui-base","name":"Dashboard","path":"/dashboard"},{"id":"e0714ab1e4e245cf","type":"ui-theme","name":"Normal","colors":{"surface":"#ffffff","primary":"#0094ce","bgPage":"#eeeeee","groupBg":"#ffffff","groupOutline":"#cccccc"}}]

Display live SIP Trunk status via RisPort

This flow will collect RisPort data for all SIP trunks and display on dashboard. This flow could be edited to notify users or kick off another flow if a device status changes.

Node-RED dashboard displaying RisPort Status

Import this flow with the following JSON:

[{"id":"60644299e58c7c53","type":"subflow","name":"Set Global Variables","info":"","category":"","in":[{"x":40,"y":80,"wires":[{"id":"f08e510bbf870d27"}]}],"out":[{"x":400,"y":80,"wires":[{"id":"f08e510bbf870d27","port":0}]}],"env":[],"meta":{},"color":"#DDAA99"},{"id":"f08e510bbf870d27","type":"change","z":"60644299e58c7c53","name":"Set Global Variables","rules":[{"t":"set","p":"hostname","pt":"global","to":"hq-cucm-pub.abc.inc","tot":"str"},{"t":"set","p":"username","pt":"global","to":"administrator","tot":"str"},{"t":"set","p":"password","pt":"global","to":"ciscopsdt","tot":"str"},{"t":"set","p":"version","pt":"global","to":"14.0","tot":"str"},{"t":"set","p":"ciscoAxlOpts","pt":"global","to":"{\"clean\":true,\"removeAttributes\":false,\"dataContainerIdentifierTails\":\"_data\"}","tot":"json"},{"t":"set","p":"debug","pt":"global","to":"false","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":220,"y":80,"wires":[[]]},{"id":"48cde6c4422a5a97","type":"tab","label":"Risport - SIP Trunk","disabled":false,"info":"","env":[]},{"id":"979fb92abd23f8f3","type":"group","z":"48cde6c4422a5a97","style":{"stroke":"#999999","stroke-opacity":"1","fill":"none","fill-opacity":"1","label":true,"label-position":"nw","color":"#a4a4a4"},"nodes":["637b67114430621b","d6fa4ed5321c0673","f4e11d2bfbde582c","ee3ffc4226568775","f0e756bd39c746b0"],"x":34,"y":39,"w":1152,"h":122},{"id":"21d13d281f1ed288","type":"group","z":"48cde6c4422a5a97","style":{"stroke":"#999999","stroke-opacity":"1","fill":"none","fill-opacity":"1","label":true,"label-position":"nw","color":"#a4a4a4"},"nodes":["66edc4a60e900c35","2cbccb4ddce020ae","1c68bd2bcced9daa","8111094ba81fef3c","4d1bc2fe602434ec","15f7dc2dea1e606d","fd639c19fc2562cf","e51ec3510f19aa0a","f760985d64499d0e","ecf3f3c89350cbb4","7252a6d8b81a62c3","09a197830d815e6c","fa7993f8757100c9"],"x":34,"y":179,"w":1152,"h":422},{"id":"637b67114430621b","type":"inject","z":"48cde6c4422a5a97","g":"979fb92abd23f8f3","name":"Set Variables","props":[{"p":"payload"}],"repeat":"","crontab":"","once":true,"onceDelay":0.1,"topic":"","payload":"{\"variables\":\"set\",\"flow\":\"SIP Risport\"}","payloadType":"json","x":350,"y":120,"wires":[["d6fa4ed5321c0673"]],"outputLabels":["Payload"],"info":"# Introduction\n\nWhere we set the initial payload. This will be the where results are stored as it flows thru nodes."},{"id":"d6fa4ed5321c0673","type":"subflow:60644299e58c7c53","z":"48cde6c4422a5a97","g":"979fb92abd23f8f3","name":"Set Global Variables","x":680,"y":120,"wires":[["f4e11d2bfbde582c"]]},{"id":"f4e11d2bfbde582c","type":"debug","z":"48cde6c4422a5a97","g":"979fb92abd23f8f3","name":"Debug payload","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1060,"y":120,"wires":[]},{"id":"66edc4a60e900c35","type":"function","z":"48cde6c4422a5a97","g":"21d13d281f1ed288","name":"SelectCmDeviceExt (SIPTrunk)","func":"try {\n let service = new ciscoRisport(global.get(\"hostname\"), global.get(\"username\"), global.get(\"password\"));\n\n var newMsg;\n var results = await service.selectCmDevice(\"SelectCmDeviceExt\", 1000, \"SIPTrunk\", \"\", \"Any\", \"\", \"Name\", \"\", \"Any\", \"Any\")\n\n // Let's find our results on the cluster and return just the array\n if (Array.isArray(results)) {\n results.map((item) => {\n if (item.ReturnCode === \"Ok\" && \"CmDevices\" in item) {\n if (Array.isArray(item?.CmDevices?.item)) {\n newMsg = item?.CmDevices?.item;\n }\n }\n })\n }else{\n newMsg = results.CmDevices?.item;\n }\n\n msg = {\n payload: newMsg\n };\n\n return msg;\n} catch (error) {\n let errMsg = null\n errMsg = JSON.stringify(error);\n throw new Error(errMsg)\n}","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[{"var":"ciscoRisport","module":"cisco-risport"}],"x":470,"y":300,"wires":[["ecf3f3c89350cbb4","fa7993f8757100c9"]]},{"id":"2cbccb4ddce020ae","type":"inject","z":"48cde6c4422a5a97","g":"21d13d281f1ed288","name":"","props":[{"p":"payload"}],"repeat":"60","crontab":"","once":true,"onceDelay":0.1,"topic":"","payload":"{}","payloadType":"str","x":510,"y":220,"wires":[["66edc4a60e900c35"]]},{"id":"1c68bd2bcced9daa","type":"debug","z":"48cde6c4422a5a97","g":"21d13d281f1ed288","name":"Debug payload","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1040,"y":280,"wires":[]},{"id":"8111094ba81fef3c","type":"comment","z":"48cde6c4422a5a97","g":"21d13d281f1ed288","name":"View @ http://<node-red ip add>:<port>/dashboard/risport","info":"","x":930,"y":360,"wires":[]},{"id":"4d1bc2fe602434ec","type":"catch","z":"48cde6c4422a5a97","g":"21d13d281f1ed288","name":"Catch Error","scope":null,"uncaught":false,"x":370,"y":560,"wires":[["15f7dc2dea1e606d"]]},{"id":"15f7dc2dea1e606d","type":"function","z":"48cde6c4422a5a97","g":"21d13d281f1ed288","name":"Error Function","func":"\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":720,"y":560,"wires":[["fd639c19fc2562cf"]]},{"id":"fd639c19fc2562cf","type":"debug","z":"48cde6c4422a5a97","g":"21d13d281f1ed288","name":"Debug error","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"error","targetType":"msg","statusVal":"","statusType":"auto","x":1070,"y":560,"wires":[]},{"id":"e51ec3510f19aa0a","type":"comment","z":"48cde6c4422a5a97","g":"21d13d281f1ed288","name":"Error Handling","info":"","x":370,"y":520,"wires":[]},{"id":"f760985d64499d0e","type":"ui-table","z":"48cde6c4422a5a97","g":"21d13d281f1ed288","group":"95bc7cf972bdbb84","name":"RisPort Table","label":"text","order":0,"width":0,"height":0,"maxrows":0,"autocols":true,"columns":[],"x":1070,"y":420,"wires":[]},{"id":"ecf3f3c89350cbb4","type":"function","z":"48cde6c4422a5a97","g":"21d13d281f1ed288","name":"Create array for table","func":"var table = [];\n\n\nfor (let i = 0; i < msg.payload.length ; i++) {\n var json = {\n \"Name\":msg.payload[i].Name,\n \"Description\": msg.payload[i].Description,\n \"Status\": msg.payload[i].Status\n }\n table.push(json);\n}\n\nvar newMsg = {\n payload: table\n}\n\nreturn newMsg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":440,"y":420,"wires":[["7252a6d8b81a62c3"]]},{"id":"7252a6d8b81a62c3","type":"ui-markdown","z":"48cde6c4422a5a97","g":"21d13d281f1ed288","group":"95bc7cf972bdbb84","name":"","order":0,"width":0,"height":0,"content":"> Trunk status updated every 60 seconds","className":"","x":870,"y":420,"wires":[["f760985d64499d0e"]]},{"id":"ee3ffc4226568775","type":"comment","z":"48cde6c4422a5a97","g":"979fb92abd23f8f3","name":"CHANGE ME.","info":"# Edit Subflow\n\nUpdate credentials to set global variables.","x":690,"y":80,"wires":[]},{"id":"09a197830d815e6c","type":"comment","z":"48cde6c4422a5a97","g":"21d13d281f1ed288","name":"Set to Auto Run & Repeat Every 60 Seconds ->","info":"","x":240,"y":220,"wires":[]},{"id":"fa7993f8757100c9","type":"switch","z":"48cde6c4422a5a97","g":"21d13d281f1ed288","name":"Debug Variable Check","property":"debug","propertyType":"global","rules":[{"t":"eq","v":"true","vt":"str"}],"checkall":"true","repair":false,"outputs":1,"x":800,"y":280,"wires":[["1c68bd2bcced9daa"]]},{"id":"f0e756bd39c746b0","type":"comment","z":"48cde6c4422a5a97","g":"979fb92abd23f8f3","name":"Set to Auto Run ->","info":"First node will initalize flow.\n\nMake sure your AXL settings are correct in the 'Set Global Variables' node.\n\nThis will auto run every time you \"Deploy\" the flow.","x":150,"y":120,"wires":[]},{"id":"95bc7cf972bdbb84","type":"ui-group","name":"SIP Trunk Status","page":"f7d829debcf17f2a","width":"12","height":"1","order":-1,"disp":true},{"id":"f7d829debcf17f2a","type":"ui-page","name":"RisPort Dashboard","ui":"20412f87db1b0cd7","path":"/risport","layout":"grid","theme":"e0714ab1e4e245cf","order":-1},{"id":"20412f87db1b0cd7","type":"ui-base","name":"Dashboard","path":"/dashboard"},{"id":"e0714ab1e4e245cf","type":"ui-theme","name":"Normal","colors":{"surface":"#ffffff","primary":"#0094ce","bgPage":"#eeeeee","groupBg":"#ffffff","groupOutline":"#cccccc"}}]

Download “Cisco CallManager” Trace files via DIME

This flow uses the DIME API to pull logs from Cisco Unified Communication Manager. Files are retrieved from server and made available to download via the dashboard. This example could be tied to a CRM to pull down files within x mintue(s) of receiving a trouble ticket.

Node-RED dashboard allowing users to download files from server

Import this flow with the following JSON:

[{"id":"60644299e58c7c53","type":"subflow","name":"Set Global Variables","info":"","category":"","in":[{"x":40,"y":80,"wires":[{"id":"f08e510bbf870d27"}]}],"out":[{"x":400,"y":80,"wires":[{"id":"f08e510bbf870d27","port":0}]}],"env":[],"meta":{},"color":"#DDAA99"},{"id":"f08e510bbf870d27","type":"change","z":"60644299e58c7c53","name":"Set Global Variables","rules":[{"t":"set","p":"hostname","pt":"global","to":"hq-cucm-pub.abc.inc","tot":"str"},{"t":"set","p":"username","pt":"global","to":"administrator","tot":"str"},{"t":"set","p":"password","pt":"global","to":"ciscopsdt","tot":"str"},{"t":"set","p":"version","pt":"global","to":"14.0","tot":"str"},{"t":"set","p":"ciscoAxlOpts","pt":"global","to":"{\"clean\":true,\"removeAttributes\":false,\"dataContainerIdentifierTails\":\"_data\"}","tot":"json"},{"t":"set","p":"debug","pt":"global","to":"false","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":220,"y":80,"wires":[[]]},{"id":"4eb4949a15cfbbf9","type":"tab","label":"DIME - Cisco CallManager","disabled":false,"info":"## SIP Dime\n\nFlow to retrieve Call Manager trace files from the last 5 mins.\n\nUsing 'cisco-dime' package from npmjs.org.","env":[]},{"id":"7c6992d416b14aba","type":"group","z":"4eb4949a15cfbbf9","style":{"stroke":"#999999","stroke-opacity":"1","fill":"none","fill-opacity":"1","label":true,"label-position":"nw","color":"#a4a4a4"},"nodes":["f253f674aff88e31","9550cf335c64e249","aba4703861d033ab","18bfa4d27be0ae70","7c63745af14ad8fe"],"x":74,"y":59,"w":1152,"h":122},{"id":"9c74ae078987380e","type":"group","z":"4eb4949a15cfbbf9","style":{"stroke":"#999999","stroke-opacity":"1","fill":"none","fill-opacity":"1","label":true,"label-position":"nw","color":"#a4a4a4"},"nodes":["a6d622eed9041333","55e49a2e32acf307","1b9dc36afdd9b6ad","37f0ef8f8e5471fb"],"x":74,"y":199,"w":1152,"h":82},{"id":"32f116d55dd6b83e","type":"group","z":"4eb4949a15cfbbf9","style":{"stroke":"#999999","stroke-opacity":"1","fill":"none","fill-opacity":"1","label":true,"label-position":"nw","color":"#a4a4a4"},"nodes":["9b94c2199017139f","3d987d7b96751924","4f0881a104210e17","dc33bbcf800047a4","806478d4c6289a35","9f2c3c490ae26c17","6b8144193e205309","e56d02a281362c0b","34c38aa083fdcb7a","8ee6c541bfcd54c8","6f70426c70471104","c649659c9e104909","dbe72dabbb5a3c85","e744139e9c9adac9","c76e4e7587208c76","a8304d42be98c8bb","bae760f4eb3f6697","85cfccf021959b5a","fa4118a442f5d769","56d72aea29089862","cb5b1b032726f2fe","2e38f2c0f4d578af","fe2965e777b22ea0","cbc65e70f6f22c35","08842f73199d1a07","5c93fd169c77e448","e0d260d162282aa7","9ead903e437af5da","71c85c1a7673a9ee","a068219cdee9f31f","6e9f408069debfc6","f2d56abe93564bdf","01f78aa8502cc28b","93c1b35827b830bf","78f423f8f9837e49","5d75eabeb9eb57bc","adb7e2e4a3b20ab3"],"x":74,"y":299,"w":1152,"h":942},{"id":"f253f674aff88e31","type":"inject","z":"4eb4949a15cfbbf9","g":"7c6992d416b14aba","name":"Set Variables","props":[{"p":"payload"}],"repeat":"","crontab":"","once":true,"onceDelay":0.1,"topic":"","payload":"{\"variables\":\"set\",\"flow\":\"SIP DIME\"}","payloadType":"json","x":390,"y":140,"wires":[["9550cf335c64e249"]],"outputLabels":["Payload"],"info":"# Introduction\n\nWhere we set the initial payload. This will be the where results are stored as it flows thru nodes."},{"id":"9550cf335c64e249","type":"subflow:60644299e58c7c53","z":"4eb4949a15cfbbf9","g":"7c6992d416b14aba","name":"Set Global Variables","x":680,"y":140,"wires":[["aba4703861d033ab"]],"info":"## Edit\n\nEdit this subflow with your VOS creds."},{"id":"aba4703861d033ab","type":"debug","z":"4eb4949a15cfbbf9","g":"7c6992d416b14aba","name":"Debug payload","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1100,"y":140,"wires":[]},{"id":"a6d622eed9041333","type":"function","z":"4eb4949a15cfbbf9","g":"9c74ae078987380e","name":"listNodeServiceLogs","func":"try {\n var serviceLogsNames = await ciscoDime.listNodeServiceLogs(global.get(\"hostname\"), global.get(\"username\"), global.get(\"password\"));\n\n var newMsg;\n\n msg = {\n payload: serviceLogsNames\n };\n\n return msg;\n} catch (error) {\n let errMsg = null\n errMsg = JSON.stringify(error);\n throw new Error(errMsg)\n}","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[{"var":"ciscoDime","module":"cisco-dime"}],"x":680,"y":240,"wires":[["37f0ef8f8e5471fb"]]},{"id":"55e49a2e32acf307","type":"inject","z":"4eb4949a15cfbbf9","g":"9c74ae078987380e","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{}","payloadType":"str","x":370,"y":240,"wires":[["a6d622eed9041333"]]},{"id":"1b9dc36afdd9b6ad","type":"comment","z":"4eb4949a15cfbbf9","g":"9c74ae078987380e","name":"Press here ->","info":"","x":170,"y":240,"wires":[]},{"id":"37f0ef8f8e5471fb","type":"debug","z":"4eb4949a15cfbbf9","g":"9c74ae078987380e","name":"Debug payload","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1100,"y":240,"wires":[]},{"id":"9b94c2199017139f","type":"catch","z":"4eb4949a15cfbbf9","g":"32f116d55dd6b83e","name":"Catch Error","scope":null,"uncaught":false,"x":370,"y":1200,"wires":[["3d987d7b96751924"]]},{"id":"3d987d7b96751924","type":"function","z":"4eb4949a15cfbbf9","g":"32f116d55dd6b83e","name":"Error Function","func":"\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":680,"y":1200,"wires":[["4f0881a104210e17"]]},{"id":"4f0881a104210e17","type":"debug","z":"4eb4949a15cfbbf9","g":"32f116d55dd6b83e","name":"Debug error","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"error","targetType":"msg","statusVal":"","statusType":"auto","x":1110,"y":1200,"wires":[]},{"id":"dc33bbcf800047a4","type":"comment","z":"4eb4949a15cfbbf9","g":"32f116d55dd6b83e","name":"Error Handling","info":"","x":370,"y":1160,"wires":[]},{"id":"806478d4c6289a35","type":"inject","z":"4eb4949a15cfbbf9","g":"32f116d55dd6b83e","name":"Set Duration","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"duration\":180}","payloadType":"json","x":370,"y":360,"wires":[["5c93fd169c77e448"]],"outputLabels":["Payload"],"info":"We set the initial payload to {}. This will be the where results are stored as it flows thru nodes."},{"id":"9f2c3c490ae26c17","type":"function","z":"4eb4949a15cfbbf9","g":"32f116d55dd6b83e","name":"Get Operations Tags via AXL","func":"try {\n let service = new ciscoAxl(global.get(\"hostname\"), global.get(\"username\"), global.get(\"password\"), global.get(\"version\"));\n\n var newMsg;\n var operation = msg.payload.operation;\n var tags = await service.getOperationTags(operation);\n\n newMsg = {\n payload: tags,\n topic: operation,\n status: 'Get operations tags via AXL'\n };\n\n return newMsg;\n} catch (error) {\n let errMsg = null\n errMsg = JSON.stringify(error);\n throw new Error(errMsg)\n}","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[{"var":"ciscoAxl","module":"cisco-axl"}],"x":880,"y":460,"wires":[["e56d02a281362c0b","93c1b35827b830bf"]]},{"id":"6b8144193e205309","type":"comment","z":"4eb4949a15cfbbf9","g":"32f116d55dd6b83e","name":"Get All Tags needed for operation: 'executeSQLQuery'","info":"This flow will get all the operations available via AXL on this CUCM cluster.\n\nResults returned in array, filtered by keyword.","x":1000,"y":400,"wires":[]},{"id":"e56d02a281362c0b","type":"function","z":"4eb4949a15cfbbf9","g":"32f116d55dd6b83e","name":"Return Results from 'executeSQLQuery' via AXL","func":"try {\n let service = new ciscoAxl(global.get(\"hostname\"), global.get(\"username\"), global.get(\"password\"), global.get(\"version\"));\n var newMsg;\n var operation = msg.topic;\n\n msg.payload.sql = \"select name from processnode WHERE name != 'EnterpriseWideData'\";\n\n var results = await service.executeOperation(operation, msg.payload, global.get(\"ciscoAxlOpts\"));\n\n newMsg = {\n payload: results,\n topic: operation,\n status: 'Return results from executeSQLQuery via AXL'\n };\n\n return newMsg;\n} catch (error) {\n let errMsg = null\n errMsg = JSON.stringify(error);\n throw new Error(errMsg)\n}","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[{"var":"ciscoAxl","module":"cisco-axl"}],"x":340,"y":540,"wires":[["6f70426c70471104","f2d56abe93564bdf"]]},{"id":"34c38aa083fdcb7a","type":"comment","z":"4eb4949a15cfbbf9","g":"32f116d55dd6b83e","name":"Call 'executeSQLQuery' operation","info":"","x":380,"y":480,"wires":[]},{"id":"8ee6c541bfcd54c8","type":"comment","z":"4eb4949a15cfbbf9","g":"32f116d55dd6b83e","name":"Press here ->","info":"","x":170,"y":360,"wires":[]},{"id":"6f70426c70471104","type":"function","z":"4eb4949a15cfbbf9","g":"32f116d55dd6b83e","name":"selectLogFiles via DIME","func":"// Vanilla JS DateTime\nlet duration = flow.get(\"duration\");\nlet date_ob = new Date();\nlet date_ob_past = new Date(date_ob);\ndate_ob_past.setMinutes(date_ob.getMinutes() - duration);\nlet currentCalendar = date_ob.toLocaleString().split(',')[0]\nlet currentTime = date_ob.toLocaleString('en-US', { hour: 'numeric', minute: 'numeric', hour12: true })\nlet futureTime = date_ob_past.toLocaleString('en-US', { hour: 'numeric', minute: 'numeric', hour12: true })\nlet currentDateTime = currentCalendar.concat(\" \", currentTime)\nlet pastDateTime = currentCalendar.concat(\" \", futureTime)\n\nvar serverArr = msg.payload.row;\nvar logArr = [];\nvar newMsg;\n\nfor (const server of serverArr) {\n try {\n let serviceLogsNames = await ciscoDime\n .selectLogFiles(\n server.name,\n global.get(\"username\"),\n global.get(\"password\"),\n \"Cisco CallManager\",\n pastDateTime, // From Date\n currentDateTime, // To Date\n \"Client: (GMT+0:0)Greenwich Mean Time-Europe/London\"\n )\n .catch((err) => {\n return err;\n });\n\n logArr.push(serviceLogsNames);\n } catch (error) {\n let errMsg = null\n errMsg = JSON.stringify(error);\n throw new Error(errMsg)\n }\n}\n\nvar flattened = [].concat.apply([],logArr); // Flatten results\n\nnewMsg ={\n payload: flattened,\n status: 'Collect selectLogFiles via DIME'\n}\n\n\nreturn newMsg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[{"var":"ciscoDime","module":"cisco-dime"}],"x":710,"y":540,"wires":[["c649659c9e104909","a068219cdee9f31f"]]},{"id":"c649659c9e104909","type":"function","z":"4eb4949a15cfbbf9","g":"32f116d55dd6b83e","name":"getOneFile","func":"var resultsArr = msg.payload;\nvar newMsg;\n\n// Let's loop thru the servers and get the files\nvar promises = resultsArr.map(function (result) {\n return ciscoDime\n .getOneFile(\n result.server,\n global.get(\"username\"),\n global.get(\"password\"),\n result.absolutepath\n )\n .catch((err) => {\n let errMsg = null\n errMsg = JSON.stringify(err);\n throw new Error(errMsg)\n });\n})\n\nvar outArr = await Promise.all(promises).then(function (results) {\n return results\n})\n\nnewMsg = {\n payload: outArr,\n status: 'Get file(s) from server using getOneFile via DIME'\n}\n\nreturn newMsg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[{"var":"ciscoDime","module":"cisco-dime"}],"x":270,"y":620,"wires":[["cb5b1b032726f2fe","01f78aa8502cc28b"]]},{"id":"dbe72dabbb5a3c85","type":"function","z":"4eb4949a15cfbbf9","g":"32f116d55dd6b83e","name":"Set base path","func":"//restrict to /data\nvar basePath = \"/data/\";\nvar filename = msg.req.params.fn;\n\n\nif(filename.includes(\"..\\\\\")){\n msg.payload = \"Illegal file path\";\n msg.statusCode = 405;//not allowed\n return [null, msg];//fire output 2\n} else if(filename.includes(\"../\")){\n msg.payload = \"Illegal file path\";\n msg.statusCode = 405;//not allowed\n return [null, msg];//fire output 2\n} \n//TODO: add more checks\n\nmsg.filename = basePath + filename;\nreturn [msg, null];//fire output 1\n\n\n","outputs":2,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":420,"y":1000,"wires":[["c76e4e7587208c76"],["e744139e9c9adac9"]]},{"id":"e744139e9c9adac9","type":"http response","z":"4eb4949a15cfbbf9","g":"32f116d55dd6b83e","name":"","statusCode":"","headers":{},"x":870,"y":1040,"wires":[]},{"id":"c76e4e7587208c76","type":"file in","z":"4eb4949a15cfbbf9","g":"32f116d55dd6b83e","name":"","filename":"filename","filenameType":"msg","format":"","chunk":false,"sendError":false,"encoding":"none","allProps":false,"x":640,"y":980,"wires":[["e744139e9c9adac9"]]},{"id":"a8304d42be98c8bb","type":"catch","z":"4eb4949a15cfbbf9","g":"32f116d55dd6b83e","name":"","scope":null,"uncaught":false,"x":200,"y":1080,"wires":[["bae760f4eb3f6697","85cfccf021959b5a"]]},{"id":"bae760f4eb3f6697","type":"function","z":"4eb4949a15cfbbf9","g":"32f116d55dd6b83e","name":"Set 404","func":"msg.payload = msg.error;\nmsg.statusCode = 404;//resource not found\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":640,"y":1080,"wires":[["e744139e9c9adac9"]]},{"id":"85cfccf021959b5a","type":"debug","z":"4eb4949a15cfbbf9","g":"32f116d55dd6b83e","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":230,"y":1120,"wires":[]},{"id":"fa4118a442f5d769","type":"comment","z":"4eb4949a15cfbbf9","g":"32f116d55dd6b83e","name":"Create http endpoint @ http://<node-red ip add>:<port>/files/data/xxx where xxx is the file name to download","info":"# Example\n\nhttp://localhost:1880/files/data/SDL001_100_000120.txt.gz","x":510,"y":940,"wires":[]},{"id":"56d72aea29089862","type":"http in","z":"4eb4949a15cfbbf9","g":"32f116d55dd6b83e","name":"","url":"/files/data/:fn","method":"get","upload":false,"swaggerDoc":"","x":190,"y":1000,"wires":[["dbe72dabbb5a3c85"]]},{"id":"cb5b1b032726f2fe","type":"split","z":"4eb4949a15cfbbf9","g":"32f116d55dd6b83e","name":"Split Results","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":270,"y":700,"wires":[["fe2965e777b22ea0"]]},{"id":"2e38f2c0f4d578af","type":"file","z":"4eb4949a15cfbbf9","g":"32f116d55dd6b83e","name":"Create files","filename":"filename","filenameType":"msg","appendNewline":true,"createDir":false,"overwriteFile":"true","encoding":"none","x":530,"y":780,"wires":[["cbc65e70f6f22c35"]]},{"id":"fe2965e777b22ea0","type":"function","z":"4eb4949a15cfbbf9","g":"32f116d55dd6b83e","name":"Format Create File Msg","func":"var newMsg;\n\n// Change output file name to whatever you'd like\nvar filename = msg.payload.filename.substring(\n msg.payload.filename.lastIndexOf(\"/\") + 1\n); // Let's get the file name from the full path\n\nvar parts = msg.parts;\nparts.filename = \"/data/\" + filename;\n\nnewMsg = {\n payload: msg.payload.data,\n filename: \"/data/\" + filename,\n parts: parts\n}\n\nreturn newMsg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":310,"y":780,"wires":[["2e38f2c0f4d578af"]]},{"id":"cbc65e70f6f22c35","type":"join","z":"4eb4949a15cfbbf9","g":"32f116d55dd6b83e","name":"","mode":"custom","build":"array","property":"filename","propertyType":"msg","key":"filename","joiner":"\\n","joinerType":"str","accumulate":false,"timeout":"","count":"","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"num","reduceFixup":"","x":270,"y":860,"wires":[["e0d260d162282aa7"]]},{"id":"08842f73199d1a07","type":"ui-form","z":"4eb4949a15cfbbf9","g":"32f116d55dd6b83e","name":"Get Duration","group":"84b7c5aaffe22fcd","label":"Enter duration in minutes to pull Cisco CallManager logs for:","order":0,"width":0,"height":0,"options":[{"label":"Duration","key":"duration","type":"number","required":true,"rows":null}],"formValue":{"duration":""},"payload":"","submit":"submit","cancel":"clear","resetOnSubmit":true,"topic":"topic","topicType":"msg","splitLayout":"","className":"","x":350,"y":420,"wires":[["5c93fd169c77e448"]]},{"id":"5c93fd169c77e448","type":"change","z":"4eb4949a15cfbbf9","g":"32f116d55dd6b83e","name":"Set Payload & Flow Variables","rules":[{"t":"set","p":"duration","pt":"flow","to":"payload.duration","tot":"msg"},{"t":"set","p":"payload","pt":"msg","to":"{\"operation\":\"executeSQLQuery\"}","tot":"json"},{"t":"set","p":"status","pt":"msg","to":"Collecting data….","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":640,"y":380,"wires":[["9f2c3c490ae26c17","6e9f408069debfc6"]],"info":"# Introduction\n\nUse this node to set variables that are unique to this flow within a project. Variables can then be accessed in other nodes with the following:\n\n``\nflow.get(\"variable_name\");\n``"},{"id":"e0d260d162282aa7","type":"function","z":"4eb4949a15cfbbf9","g":"32f116d55dd6b83e","name":"Format for VUE template","func":"var newMsg;\nvar fileNames = msg.filename;\nvar output = []\n\nfor (const file of fileNames) {\n var filename = file.substring(\n file.lastIndexOf(\"/\") + 1\n );\n\n let json = {\n link: 'https://nodered.ubuntu.automate.builders/files' + file,\n title: filename\n }\n output.push(json);\n}\n\nnewMsg = {\n payload: output\n}\n\nreturn newMsg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[{"var":"utils","module":"util"}],"x":490,"y":860,"wires":[["9ead903e437af5da"]]},{"id":"9ead903e437af5da","type":"ui-template","z":"4eb4949a15cfbbf9","g":"32f116d55dd6b83e","group":"84b7c5aaffe22fcd","dashboard":"20412f87db1b0cd7","page":"0a87b32a472e0d54","name":"DIME Links","order":0,"width":0,"height":0,"head":"","format":"<div>\n <v-sheet class=\"pa-md-4 mx-lg-auto\" :elevation=\"24\" border color=\"success\" rounded v-if=\"msg?.status\">{{ msg?.status }}</v-sheet>\n <div v-for=\"(url, index) in msg.payload\" :key=\"index\">\n <a v-if=\"url.link\" :href=\"url.link\" target=\"_blank\">{{ url.title }}</a>\n <span v-else>{{ url.title }}</span>\n </div>\n</div>","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":true,"templateScope":"local","className":"","x":1090,"y":860,"wires":[[]]},{"id":"71c85c1a7673a9ee","type":"link in","z":"4eb4949a15cfbbf9","g":"32f116d55dd6b83e","name":"Status Messages In","links":["01f78aa8502cc28b","6e9f408069debfc6","a068219cdee9f31f","f2d56abe93564bdf","93c1b35827b830bf"],"x":725,"y":720,"wires":[["9ead903e437af5da","adb7e2e4a3b20ab3"]]},{"id":"a068219cdee9f31f","type":"link out","z":"4eb4949a15cfbbf9","g":"32f116d55dd6b83e","name":"Status Message Out","mode":"link","links":["71c85c1a7673a9ee"],"x":965,"y":540,"wires":[]},{"id":"6e9f408069debfc6","type":"link out","z":"4eb4949a15cfbbf9","g":"32f116d55dd6b83e","name":"Status Message Out","mode":"link","links":["71c85c1a7673a9ee"],"x":865,"y":340,"wires":[]},{"id":"f2d56abe93564bdf","type":"link out","z":"4eb4949a15cfbbf9","g":"32f116d55dd6b83e","name":"Status Message Out","mode":"link","links":["71c85c1a7673a9ee"],"x":645,"y":620,"wires":[]},{"id":"01f78aa8502cc28b","type":"link out","z":"4eb4949a15cfbbf9","g":"32f116d55dd6b83e","name":"Status Message Out","mode":"link","links":["71c85c1a7673a9ee"],"x":475,"y":620,"wires":[]},{"id":"93c1b35827b830bf","type":"link out","z":"4eb4949a15cfbbf9","g":"32f116d55dd6b83e","name":"Status Message Out","mode":"link","links":["71c85c1a7673a9ee"],"x":1105,"y":460,"wires":[]},{"id":"78f423f8f9837e49","type":"comment","z":"4eb4949a15cfbbf9","g":"32f116d55dd6b83e","name":"Get status messages to update dashboard","info":"","x":680,"y":680,"wires":[]},{"id":"18bfa4d27be0ae70","type":"comment","z":"4eb4949a15cfbbf9","g":"7c6992d416b14aba","name":"CHANGE ME.","info":"# Edit Subflow\n\nUpdate credentials to set global variables.","x":690,"y":100,"wires":[]},{"id":"5d75eabeb9eb57bc","type":"debug","z":"4eb4949a15cfbbf9","g":"32f116d55dd6b83e","name":"Debug payload","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1100,"y":800,"wires":[]},{"id":"adb7e2e4a3b20ab3","type":"switch","z":"4eb4949a15cfbbf9","g":"32f116d55dd6b83e","name":"Debug Variable Check","property":"debug","propertyType":"global","rules":[{"t":"eq","v":"true","vt":"str"}],"checkall":"true","repair":false,"outputs":1,"x":940,"y":720,"wires":[["5d75eabeb9eb57bc"]]},{"id":"7c63745af14ad8fe","type":"comment","z":"4eb4949a15cfbbf9","g":"7c6992d416b14aba","name":"Set to Auto Run ->","info":"First node will initalize flow.\n\nMake sure your AXL settings are correct in the 'Set Global Variables' node.\n\nThis will auto run every time you \"Deploy\" the flow.","x":190,"y":140,"wires":[]},{"id":"84b7c5aaffe22fcd","type":"ui-group","name":"SIP DIME","page":"e1de64c15eccdf16","width":"6","height":"1","order":-1,"disp":true},{"id":"20412f87db1b0cd7","type":"ui-base","name":"Dashboard","path":"/dashboard"},{"id":"0a87b32a472e0d54","type":"ui-page","name":"AXL Dashboard","ui":"20412f87db1b0cd7","path":"/axl","layout":"grid","theme":"e0714ab1e4e245cf","order":-1},{"id":"e1de64c15eccdf16","type":"ui-page","name":"DIME Dashboard","ui":"20412f87db1b0cd7","path":"/dime","layout":"grid","theme":"e0714ab1e4e245cf","order":-1},{"id":"e0714ab1e4e245cf","type":"ui-theme","name":"Normal","colors":{"surface":"#ffffff","primary":"#0094ce","bgPage":"#eeeeee","groupBg":"#ffffff","groupOutline":"#cccccc"}}]

Hopefully this boilerplate and examples will be useful in your DevOps workflow!

As always if you would like to support my work, you can always buy me a coffee. I would really appreciate it (but is not required).

Thanks and enjoy!

--

--