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

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 3 months, 3 weeks ago
Rating: not yet rated

Owner

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