Dynamic Network Map

Dynamic Network Map

map

This map is created via an object and pings all the devices once, every 30 seconds

Requirements:

  • Linux based OS due to use of ping parameters and grep use.
  • Dashboard nodes (node-red-dashboard)

The flow currently has 2 types of information that can be displayed when a node is clicked in the network map:

  • ping
  • tasmota

With "ping" it will try to ping and display the rtt/packet loss

With "tasmota" it will use a http request node to execute the status 0 and return the ssid/version/rssi. You can click the ip address to open the tasmota ui in a new tab. Note: if you use user/password for tasmota device this will not work. Modify the flow to your liking.

Template nodes are used to format the output per type.

The switch node can be used to add/remove others

Detail

It creates a web location: /networkapi that shows the topology and is read by the network map in the ui-template node

The map is refreshed every 30 seconds, can be changed in the ui-template node

flow

[{"id":"90831ff5.70c858","type":"http in","z":"674d3c11.099964","name":"","url":"/networkapi","method":"get","upload":false,"swaggerDoc":"","x":327,"y":192,"wires":[["9d0f8230.226f88"]],"l":false},{"id":"e82032cc.66ced8","type":"http response","z":"674d3c11.099964","name":"","statusCode":"","headers":{},"x":423,"y":192,"wires":[],"l":false},{"id":"9d0f8230.226f88","type":"change","z":"674d3c11.099964","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"network","tot":"global"}],"action":"","property":"","from":"","to":"","reg":false,"x":375,"y":192,"wires":[["e82032cc.66ced8"]],"l":false},{"id":"62016808.314be8","type":"function","z":"674d3c11.099964","name":"","func":"m = msg.payload\nglobal.set('network',m)\n\nreturn {payload:global.get('network')}","outputs":1,"noerr":0,"x":159,"y":192,"wires":[["b5f63d1d.57a9b8"]],"l":false},{"id":"b5f63d1d.57a9b8","type":"debug","z":"674d3c11.099964","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","x":207,"y":192,"wires":[],"l":false},{"id":"6058a657.1e87d8","type":"change","z":"674d3c11.099964","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"network","tot":"global"},{"t":"set","p":"payload","pt":"msg","to":"**.ip","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":159,"y":312,"wires":[["2c92f82c.ae7c3"]],"l":false},{"id":"2c92f82c.ae7c3","type":"function","z":"674d3c11.099964","name":"","func":"m = msg.payload\n\nfor(x=0;x<m.length;x++){\nnode.send({payload:m[x],ip:m[x]})    \n    \n}\n","outputs":1,"noerr":0,"x":215,"y":312,"wires":[["36739bd4.08a97c"]],"l":false},{"id":"36739bd4.08a97c","type":"exec","z":"674d3c11.099964","command":"ping -c1","addpay":true,"append":"| grep -i \"100%\"","useSpawn":"false","timer":"","oldrc":false,"name":"","x":279,"y":312,"wires":[["6eb947a3.51da88"],[],[]],"l":false},{"id":"6eb947a3.51da88","type":"function","z":"674d3c11.099964","name":"","func":"n = global.get('network')\nip = msg.ip\nstate = (msg.payload===\"\")?true:false\n\nreturn {network:n,ipaddress:ip,state:state}","outputs":1,"noerr":0,"x":351,"y":312,"wires":[["55ea9ebe.c981b"]],"l":false},{"id":"55ea9ebe.c981b","type":"change","z":"674d3c11.099964","name":"","rules":[{"t":"set","p":"network","pt":"global","to":"\t$globalContext('network') ~>| **.properties[$contains(ip,$$.ipaddress)] |{\"reachable\":$boolean($$.state)} |","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":423,"y":312,"wires":[[]],"l":false},{"id":"4351f652.050938","type":"comment","z":"674d3c11.099964","name":"Update Network Status","info":"","x":172,"y":264,"wires":[]},{"id":"88de01e0.6d774","type":"comment","z":"674d3c11.099964","name":"Network - JSON API","info":"","x":402,"y":144,"wires":[]},{"id":"c32250dd.48ba3","type":"comment","z":"674d3c11.099964","name":"Set Topology","info":"","x":142,"y":144,"wires":[]},{"id":"51a55089.d0c8e8","type":"inject","z":"674d3c11.099964","name":"","topic":"","payload":"","payloadType":"date","repeat":"30","crontab":"","once":true,"onceDelay":0.1,"x":111,"y":312,"wires":[["6058a657.1e87d8"]],"l":false},{"id":"71c08d4e.d425fc","type":"ui_template","z":"674d3c11.099964","group":"948b2f03.477ee","name":"","order":1,"width":0,"height":0,"format":"<style>\n.nr-dashboard-template .ng-scope ._md .visible,.nr-dashboard-theme .ui-card-panel{background:transparent!important}\n\n.device{font-weight:bold}\ncircle {\n\tfill: #090;\n\tstroke: #090;\n\tstroke-width: 1.5px;\n}\ncircle.unreachable {\n\tfill: #f00;\n\tstroke: #f00;\n\tstroke-width: 1.5px;\n}\ncircle.reachable {\n\tfill: #090;\n\tstroke: #090;\n\tstroke-width: 1.5px;\n}\n.node,\ntext {\n\tfont: 10px 'Helvetica Neue';\n}\n.link {\n\tfill: none;\n\tstroke: #888;\n\tstroke-width: 1px;\n\tstroke-opacity: 1;\n}\n.reachable {\n\tfill: none;\n\tstroke: #090;\n}\n.unreachable {\n\tfill: none;\n\tstroke: #f30;\n}\n\n#chart {\n\twidth: 800px;\n\tmargin: 20px auto;\n\n}\n#chart path{\n    fill:none;\n}\n#info{position:absolute;bottom:8px;}\n#info p{\n    padding:8px;\n    font-size:11px;\n}\n#info a{text-decoration:none;color:#f90}\n.weight{font-weight:bold}\n</style>\n<script type=\"text/javascript\">\nfunction renderNetwork() {\n    $(\"body.nr-dashboard-theme .md-content .md-card,.nr-dashboard-theme .md-content .md-card,.nr-dashboard-theme .ui-card-panel\").css(\"background-color\",\"transparent!important\")\n\tvar w = 600,\n\t\th = 600;\n\n\tvar cluster = d3.layout.cluster().size([h, w - 200]);\n\n\tvar diagonal = d3.svg.diagonal().projection(function (d) {\n\t\treturn [d.y, d.x];\n\t});\n\n\tvar vis = d3\n\t\t.select('#chart')\n\t\t.append('svg:svg')\n\t\t.attr('width', w)\n\t\t.attr('height', h)\n\t\t.append('svg:g')\n\t\t.attr('transform', 'translate(70, 0)');\n\n\td3.json('../networkapi', function (json) {\n\t\tvar nodes = cluster.nodes(json);\n\n\t\tvar link = vis\n\t\t\t.selectAll('path.link')\n\t\t\t.data(cluster.links(nodes))\n\t\t\t.enter()\n\t\t\t.append('svg:path')\n\n\t\t\t.attr('class', 'link')\n\t\t\t.attr('d', diagonal);\n\n\t\tvar node = vis\n\t\t\t.selectAll('g.node')\n\t\t\t.data(nodes)\n\t\t\t.enter()\n\t\t\t.append('svg:g')\n\n\t\t\t.attr('transform', function (d) {\n\t\t\t\treturn 'translate(' + d.y + ',' + d.x + ')';\n\t\t\t});\n\n\t\tnode.append('svg:circle').attr('r', 4.5);\n\t\n\t\tnode.append('svg:text')\n\t\t\t.attr('dx', function (d) {\n\t\t\t\treturn d.children ? -8 : 8;\n\t\t\t})\n\n\t\t\t.attr('dy', 3)\n\t\t\t.attr('text-anchor', function (d) {\n\t\t\t\treturn d.children ? 'end' : 'start';\n\t\t\t})\n\t\t\t.attr('stroke-opacity', 0)\n\t\t\t.attr('fill', '#fff')\n\t\t\t.text(function (d) {\n\t\t\t\treturn d.name;\n\t\t\t})\n\t\t\t.attr('cursor', 'pointer')\n\t\t\t.on('mouseover', mouseover)\n\t\t\t.on('mouseout', mouseout)\n\t\t\t.on('click', info);\n\n\t\tvis.selectAll('circle')\n\t\t\t.filter(function (d) {\n\t\t\t\t//console.log(d.properties.reachable);\n\t\t\t\treturn d;\n\t\t\t})\n\t\t\t.attr('class', function (d, i) {\n\t\t\t//\tconsole.log(d, i);\n\t\t\t\tif (d.properties.reachable) {\n\t\t\t\t\treturn ' reachable';\n\t\t\t\t} else {\n\t\t\t\t\treturn ' unreachable';\n\t\t\t\t}\n\t\t\t});\n\t});\n}\n\nfunction updateNetwork() {\n    console.log('update')\n\td3.select('#chart svg').remove();\n\n\trenderNetwork();\n}\nfunction mouseover(d, i) {\n\td3.select(this).attr({\n\t\tfill: 'orange',\n\t});\n}\nfunction mouseout(d, i) {\n\td3.select(this).attr({\n\t\tfill: '#fff',\n\t});\n}\nfunction info(d, i) {\n    if(d.properties.type===\"ping\"){\n        $('#info').html(\"<p>Pinging...</p>\") \n    }\n    else{\n        $('#info').html(\"<p>Loading...</p>\")\n    }\n\n\tsc.send({payload:{name:d.name,ip:d.properties.ip,type:d.properties.type}});\n}\nrenderNetwork()\n\nvar sc = scope;\n\nint = setInterval(function(){\n    updateNetwork()\n},30000)\n</script>\n<script>\n(function(scope) {\n  scope.$watch('msg', function(msg) {\n    if (msg.update) {\n      // Do something when msg arrives\n      updateNetwork()\n     // $(\"#my_\"+scope.$id).html(msg.payload);\n    }\n    if (msg.info){\n        $(\"#info\").hide().html(\"\").html(msg.info).fadeIn()\n    }\n  });\n})(scope);\n</script>\n<div id=\"chart\"></div>\n<div id=\"info\"></div>\n    \n","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":true,"templateScope":"local","x":255,"y":480,"wires":[["3ad6a50b.144c0a"]],"l":false},{"id":"ebb36c06.8fb5a","type":"comment","z":"674d3c11.099964","name":"Render Network Map","info":"","x":172,"y":384,"wires":[]},{"id":"41f7dc80.724e64","type":"inject","z":"674d3c11.099964","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":true,"onceDelay":0.1,"x":111,"y":456,"wires":[["afb1db1b.fa126"]],"l":false},{"id":"fc9ae982.d0555","type":"function","z":"674d3c11.099964","name":"","func":"return {update:true}","outputs":1,"noerr":0,"x":207,"y":456,"wires":[["71c08d4e.d425fc"]],"l":false},{"id":"3ad6a50b.144c0a","type":"switch","z":"674d3c11.099964","name":"","property":"payload.type","propertyType":"msg","rules":[{"t":"eq","v":"tasmota","vt":"str"},{"t":"eq","v":"ping","vt":"str"}],"checkall":"true","repair":false,"outputs":2,"x":303,"y":480,"wires":[["cd498e21.cfd81"],["43c44754.63804"]],"l":false},{"id":"cd498e21.cfd81","type":"function","z":"674d3c11.099964","name":"","func":"u = \"http://\"\nm = msg.payload\n\nurl = \"http://\"+m.ip+\"/cm?cmnd=status%200\"\n\n\nreturn {url:url,node:m.name,ip:m.ip}","outputs":1,"noerr":0,"x":351,"y":456,"wires":[["a6b24bf8.904898"]],"l":false},{"id":"a6b24bf8.904898","type":"http request","z":"674d3c11.099964","name":"","method":"GET","ret":"obj","paytoqs":false,"url":"","tls":"","persist":false,"proxy":"","authType":"","x":399,"y":456,"wires":[["db490bc9.521ca8"]],"l":false},{"id":"db490bc9.521ca8","type":"template","z":"674d3c11.099964","name":"Tasmota","field":"info","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"<p>\n<span class='weight'>{{node}}</span><br/>\n<span class='weight'>IP</span> <a href=\"http://{{ip}}\" target=\"_blank\">{{ip}}</a>\n<span class='weight'>Uptime</span> {{payload.StatusSTS.Uptime}} \n<span class='weight'>Version</span> {{payload.StatusFWR.Version}} \n<span class='weight'>SSId</span> {{payload.StatusSTS.Wifi.SSId}} \n<span class='weight'>RSSI</span> {{payload.StatusSTS.Wifi.RSSI}} \n</p>","output":"str","x":492,"y":456,"wires":[["4d477f4c.f0314"]]},{"id":"4d477f4c.f0314","type":"link out","z":"674d3c11.099964","name":"tasmota template out","links":["46559f3.46bf56"],"x":615,"y":480,"wires":[]},{"id":"46559f3.46bf56","type":"link in","z":"674d3c11.099964","name":"","links":["4d477f4c.f0314"],"x":207,"y":504,"wires":[["71c08d4e.d425fc"]]},{"id":"43c44754.63804","type":"function","z":"674d3c11.099964","name":"","func":"m = msg.payload\ncmd = \"ping -c4 \"+msg.payload.ip +\" | tail -n2\"\n\nreturn {payload:cmd,node:m.name,ip:m.ip}","outputs":1,"noerr":0,"x":351,"y":504,"wires":[["6ba88c05.e45f14"]],"l":false},{"id":"6ba88c05.e45f14","type":"exec","z":"674d3c11.099964","command":"","addpay":true,"append":"","useSpawn":"false","timer":"","oldrc":false,"name":"","x":399,"y":504,"wires":[["a9061c41.5ef538"],[],[]],"l":false},{"id":"a9061c41.5ef538","type":"template","z":"674d3c11.099964","name":"Ping","field":"info","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"<p>\n<span class='weight'>{{node}} ({{ip}})</span><br/>\n{{payload}}\n</p>","output":"str","x":482,"y":504,"wires":[["4d477f4c.f0314"]]},{"id":"84782896.673eb8","type":"comment","z":"674d3c11.099964","name":"Help - see Info tab","info":"### Set Topology in inject node\n\nFormat the network topology as (can have as many depths/connections as you like):\n\n```\n{\n  \"name\": \"Internet\",\n  \"properties\": {\n    \"type\": \"ping\",\n    \"reachable\": false,\n    \"ip\": \"google.com\"\n  },\n  \"children\": [\n    {\n      \"name\": \"Internet Router\",\n      \"properties\": {\n        \"type\": \"ping\",\n        \"reachable\": false,\n        \"ip\": \"192.168.1.1\"\n      },\n      \"children\": [\n        {\n          \"name\": \"Switch 1\",\n          \"properties\": {\n            \"type\": \"ping\",\n            \"reachable\": false,\n            \"ip\": \"192.168.1.10\"\n          },\n          \"children\": [\n            {\n              \"name\": \"Switch 2\",\n              \"properties\": {\n                \"type\": \"ping\",\n                \"reachable\": false,\n                \"ip\": \"192.168.1.20\"\n              },\n              \"children\": [\n                {\n                  \"name\": \"Computer 1\",\n                  \"properties\": {\n                    \"type\": \"ping\",\n                    \"reachable\": false,\n                    \"ip\": \"192.168.1.100\"\n                  }\n                },\n                {\n                  \"name\": \"Computer 2\",\n                  \"properties\": {\n                    \"type\": \"ping\",\n                    \"reachable\": false,\n                    \"ip\": \"192.168.1.101\"\n                  }\n                }\n              ]\n            },\n            {\n              \"name\": \"Hue Bridge\",\n              \"properties\": {\n                \"type\": \"ping\",\n                \"ip\": \"192.168.1.30\",\n                \"reachable\": false\n              }\n            },\n            {\n              \"name\": \"TV\",\n              \"properties\": {\n                \"type\": \"ping\",\n                \"ip\": \"192.168.1.40\",\n                \"reachable\": false\n              }\n            },\n            {\n              \"name\": \"NAS\",\n              \"properties\": {\n                \"type\": \"ping\",\n                \"reachable\": false,\n                \"ip\": \"192.168.1.200\"\n              }\n            }\n          ]\n        },\n        {\n          \"name\": \"Access Point\",\n          \"properties\": {\n            \"type\": \"ping\",\n            \"reachable\": false,\n            \"ip\": \"192.168.1.50\"\n          },\n          \"children\": [\n            {\n              \"name\": \"Wireless device 1\",\n              \"properties\": {\n                \"type\": \"tasmota\",\n                \"ip\": \"192.168.1.61\",\n                \"reachable\": false\n              }\n            },\n            {\n              \"name\": \"Wireless device 2\",\n              \"properties\": {\n                \"type\": \"tasmota\",\n                \"ip\": \"192.168.1.63\",\n                \"reachable\": false\n              }\n            }\n          ]\n        }\n      ]\n    }\n  ]\n}\n```\n\n### Types\n\nThe flow currently has 2 types of information that can be displayed when a node is clicked in the network map:\n\n- ping\n- tasmota\n\nWith \"ping\" it will try to ping and display the rtt/packet loss\n\nWith \"tasmota\" it will use a http request node to execute the `status 0` and return the ssid/version/rssi. You can click the ip address to open the tasmota ui in a new tab\n\nTemplate nodes are used to format the output per type.\n\nThe switch node can be used to add/remove others\n\n#### Detail\n\nIt creates a web location: /networkapi that shows the topology and is read by the network map in the ui-template node\n\nThe map is refreshed every 30 seconds, can be changed in the ui-template node","x":162,"y":72,"wires":[]},{"id":"29604fa0.ef3b98","type":"inject","z":"674d3c11.099964","name":"","topic":"","payload":"{\"name\":\"Internet\",\"properties\":{\"type\":\"ping\",\"reachable\":false,\"ip\":\"google.com\"},\"children\":[{\"name\":\"Internet Router\",\"properties\":{\"type\":\"ping\",\"reachable\":false,\"ip\":\"192.168.1.1\"},\"children\":[{\"name\":\"Switch 1\",\"properties\":{\"type\":\"ping\",\"reachable\":false,\"ip\":\"192.168.1.10\"},\"children\":[{\"name\":\"Switch 2\",\"properties\":{\"type\":\"ping\",\"reachable\":false,\"ip\":\"192.168.1.20\"},\"children\":[{\"name\":\"Computer 1\",\"properties\":{\"type\":\"ping\",\"reachable\":false,\"ip\":\"192.168.1.100\"}},{\"name\":\"Computer 2\",\"properties\":{\"type\":\"ping\",\"reachable\":false,\"ip\":\"192.168.1.101\"}}]},{\"name\":\"Hue Bridge\",\"properties\":{\"type\":\"ping\",\"ip\":\"192.168.1.30\",\"reachable\":false}},{\"name\":\"TV\",\"properties\":{\"type\":\"ping\",\"ip\":\"192.168.1.40\",\"reachable\":false}},{\"name\":\"NAS\",\"properties\":{\"type\":\"ping\",\"reachable\":false,\"ip\":\"192.168.1.200\"}}]},{\"name\":\"Access Point\",\"properties\":{\"type\":\"ping\",\"reachable\":false,\"ip\":\"192.168.1.50\"},\"children\":[{\"name\":\"Wireless device 1\",\"properties\":{\"type\":\"tasmota\",\"ip\":\"192.168.1.61\",\"reachable\":false}},{\"name\":\"Wireless device 2\",\"properties\":{\"type\":\"tasmota\",\"ip\":\"192.168.1.63\",\"reachable\":false}}]}]}]}","payloadType":"json","repeat":"","crontab":"","once":true,"onceDelay":0.1,"x":111,"y":192,"wires":[["62016808.314be8"]],"l":false},{"id":"afb1db1b.fa126","type":"delay","z":"674d3c11.099964","name":"","pauseType":"delay","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":159,"y":456,"wires":[["fc9ae982.d0555"]],"l":false},{"id":"948b2f03.477ee","type":"ui_group","z":"","name":"Network","tab":"458d5bcd.691d94","order":1,"disp":false,"width":17,"collapse":false},{"id":"458d5bcd.691d94","type":"ui_tab","z":"","name":"Network","icon":"dashboard","order":4,"disabled":false,"hidden":false}]

Flow Info

Created 4 years, 11 months ago
Rating: 5 2

Owner

Actions

Rate:

Node Types

Core
  • change (x3)
  • comment (x5)
  • debug (x1)
  • delay (x1)
  • exec (x2)
  • function (x6)
  • http in (x1)
  • http request (x1)
  • http response (x1)
  • inject (x3)
  • link in (x1)
  • link out (x1)
  • switch (x1)
  • template (x2)
Other
  • ui_group (x1)
  • ui_tab (x1)
  • ui_template (x1)

Tags

  • network
  • dashboard
  • template
  • map
  • tasmota
Copy this flow JSON to your clipboard and then import into Node-RED using the Import From > Clipboard (Ctrl-I) menu option