phyunsj / node-red-custom-dashboard-map-page

template node to create the dashboard (+ google maps )

Home Page:https://phyunsj.github.io

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Node-RED Custom Dashboard Page + Google Maps

Build imaginary monitoring site using Node-RED template node after reading the following examples.

Node-RED nodes

  1. HTTP GET /monitornode
  2. (function node) msg.payload.apikey for Google Map API Key
  3. (template node) msg.payload.script for Javascript
  4. (template node) msg.payload.style for CSS style
  5. (template node) msg.payload = HTML template + CDN links (jquery, bootstrap, google map, etc) + msg.payload.apikey + msg.payload.script + msg.payload.style
  6. msg.payload => (HTTP Response node)

In Action

Flow

[{"id":"fe81076e.42bdc8","type":"http in","z":"934da042.4fbe4","name":"","url":"/monitor","method":"get","upload":false,"swaggerDoc":"","x":148,"y":167.0000286102295,"wires":[["fc4091d2.2f8b9"]]},{"id":"fc4091d2.2f8b9","type":"function","z":"934da042.4fbe4","name":"msg.apikey = \"<map api key>\";","func":"//Assign API Key\nmsg.payload.apikey = \"abcdefghijklmnop\";\nreturn msg;","outputs":1,"noerr":0,"x":260.00008392333984,"y":228.00005531311035,"wires":[["144cb1cf.a8969e"]]},{"id":"d5348f9d.0263c","type":"template","z":"934da042.4fbe4","name":"CSS","field":"payload.style","fieldType":"msg","format":"html","syntax":"mustache","template":"body{\n  font-family: 'Droid Sans', 'Helvetica', Arial, sans-serif;\n  margin:5px;\n}\n#map{\n  display: block;\n  width: 95%;\n  height: 500px;\n  margin: 0 auto;\n  -moz-box-shadow: 0px 5px 20px #ccc;\n  -webkit-box-shadow: 0px 5px 20px #ccc;\n  box-shadow: 0px 5px 20px #ccc;\n}\n#map.large{\n  height:600px;\n}\n\n.overlay{\n  display:block;\n  text-align:center;\n  color:#fff;\n  font-size:60px;\n  line-height:80px;\n  opacity:0.8;\n  background:#4477aa;\n  border:solid 3px #336699;\n  border-radius:4px;\n  box-shadow:2px 2px 10px #333;\n  text-shadow:1px 1px 1px #666;\n  padding:0 4px;\n}\n\n.overlay_arrow{\n  left:50%;\n  margin-left:-16px;\n  width:0;\n  height:0;\n  position:absolute;\n}\n.overlay_arrow.above{\n  bottom:-15px;\n  border-left:16px solid transparent;\n  border-right:16px solid transparent;\n  border-top:16px solid #336699;\n}\n.overlay_arrow.below{\n  top:-15px;\n  border-left:16px solid transparent;\n  border-right:16px solid transparent;\n  border-bottom:16px solid #336699;\n}\n\n#toast-container > .toast {\n    background-image: none !important;\n}\n\n#toast-container > .toast:before {\n    position: fixed;\n    font-family: FontAwesome;\n    font-size: 24px;\n    line-height: 18px;\n    float: left;\n    color: #FFF;\n    padding-right: 0.5em;\n    margin: auto 0.5em auto -1.5em;\n}        \n#toast-container > .toast-warning:before {\n    content: \"\\f119\";\n}\n#toast-container > .toast-error:before {\n    content: \"\\f119\";\n}\n#toast-container > .toast-info:before {\n    content: \"\\f005\";\n}\n#toast-container > .toast-success:before {\n    content: \"\\f002\";\n}","x":523.0000038146973,"y":227.0000286102295,"wires":[["8817e51.cef5118"]]},{"id":"144cb1cf.a8969e","type":"template","z":"934da042.4fbe4","name":"JavaScript","field":"payload.script","fieldType":"msg","format":"javascript","syntax":"plain","template":"\n/////////////////////////////////\n// Global \n\nvar loc = window.location;\nvar ws;\nvar wsUri = \"ws:\";\nvar map;\nvar markers = [];\n\nif (loc.protocol === \"https:\") { wsUri = \"wss:\"; }\n// This needs to point to the web socket in the Node-RED flow\n// ... in this case it's ws/simple\nwsUri += \"//\" + loc.host + loc.pathname.replace(\"monitor\",\"ws/alert\");\n\n//////////////////////////////////////////////\n//  google map\n\nfunction initMap() {\n    map = new google.maps.Map(document.getElementById('map'), {\n          zoom: 13,\n          center: {lat: 40.489497, lng: -74.448254}\n    });\n}\n\nfunction dropMarker (type, addr, la, lo) {\n    \n    clearMarker(addr);\n    var marker;\n    if ( type === 'Water') {\n        marker = new google.maps.Marker({\n        position: {lat: la, lng: lo},\n        title : addr,\n        map: map,\n        label : 'W',\n        animation: google.maps.Animation.DROP\n        });\n    } else {\n        marker = new google.maps.Marker({\n        position: {lat: la, lng: lo},\n        title : addr,\n        map: map,\n        label : 'G',\n        animation: google.maps.Animation.DROP\n        });    \n    }\n    \n    markers.push(marker);\n}\n\n\nfunction clearMarkers() {\n    for ( var i = 0; i < markers.length; i++) {\n        markers[i].setMap(null);\n    }\n}\n\nfunction clearMarker(addr) {\n    for ( var i = 0; i < markers.length; i++) {\n        if ( markers[i].title == addr ) markers[i].setMap(null);\n    }\n}\n\n////////////////////////////////////////////////\n// Toastr\n\nfunction showToast(type, addr, la, lo) {  \n    toastr.options.positionClass = 'toast-bottom-full-width';    toastr.options.extendedTimeOut = 0; //1000;\n    toastr.options.timeOut = 1000;\n    toastr.options.fadeOut = 250;\n    toastr.options.fadeIn = 250;\n    var msg = ' Address : ' + addr;\n    msg = msg + ', Latitude : '+ la;\n    msg = msg + ', Longitude : '+ lo;\n        \n    if ( type === 'Water') {\n        toastr.warning('<b>Water Leak</b> '+msg);\n    } else {\n        toastr.error('<b>Gas Leak</b> '+msg);\n    }\n}\n\n///////////////////////////////////////////////\n// WebSocket \n\nfunction wsConnect() {\n    \n    console.log(\"connect\",wsUri);\n    ws = new WebSocket(wsUri);\n\n    ws.onopen = function() {\n       console.log(\"connected\");\n    }\n    ws.onclose = function() {\n        setTimeout(wsConnect,5000);\n    }\n    \n    ws.onmessage = function(msg) {\n        var payload = JSON.parse(msg.data);\n        //console.log(payload);\n        showToast(payload.type, payload.addr, payload.la, payload.lo);\n        //insert Marker\n        dropMarker(payload.type, payload.addr, payload.la, payload.lo);\n    }\n}\n\nfunction action(m) {\n    // Nothing has been defined yet\n    if (ws) { ws.send(m); }\n}\n \n","x":424.0000534057617,"y":165.0000286102295,"wires":[["d5348f9d.0263c"]]},{"id":"422d2000.76f7d","type":"http response","z":"934da042.4fbe4","name":"","statusCode":"","headers":{},"x":742.0000610351562,"y":223.00005531311035,"wires":[]},{"id":"8817e51.cef5118","type":"template","z":"934da042.4fbe4","name":"HTML","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"<!doctype html>\n<html>\n<head>\n    <!-- Required meta tags -->\n    <meta charset=\"utf-8\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\">\n\n    <!-- Bootstrap CSS -->\n    <!-- Latest compiled and minified CSS -->\n    <link rel=\"stylesheet\" href=\"https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css\" integrity=\"sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u\" crossorigin=\"anonymous\">\n    <link rel=\"stylesheet\" href=\"https://use.fontawesome.com/releases/v5.1.0/css/all.css\" integrity=\"sha384-lKuwvrZot6UHsBSfcMvOkWwlCMgc0TaWr+30HWe3a4ltaBwTZhyTEggF5tJv8tbt\" crossorigin=\"anonymous\">\n    <link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/toastr.js/2.1.4/toastr.min.css\" crossorigin=\"anonymous\">\n    <style>{{{payload.style}}}</style>\n    \n    <!-- jQuery first, then Bootstrap JS -->\n    <script src=\"https://code.jquery.com/jquery-2.2.4.js\" integrity=\"sha256-iT6Q9iMJYuQiMWNd9lDyBUStIq/8PuOW33aOqmvFpqI=\"  crossorigin=\"anonymous\"></script> <!-- Latest compiled and minified JavaScript -->\n    <script src=\"https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js\" integrity=\"sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa\" crossorigin=\"anonymous\"></script>\n    <script src=\"https://cdnjs.cloudflare.com/ajax/libs/toastr.js/2.1.4/toastr.min.js\" crossorigin=\"anonymous\"></script> \n    \n    <script>{{{payload.script}}}</script>\n</head>\n<body onload=\"wsConnect();\" onunload=\"ws.disconnect();\">\n\n<div class=\"container\">\n    <div class=\"page-header\">\n    <h1>Alert Monitoring System <small> Water/Gas Leaks</small></h1>\n    </div>\n</div>\n<div class=\"container\">\n    <div class=\"row justify-content-center\">\n        <div class=\"col-12 col-lg-8 col-md-12\"> \n            <div id=\"map\"></div>\n        </div>\n    </div>\n    <br>\n    <br>\n</div>\n<script async defer\n    src=\"https://maps.googleapis.com/maps/api/js?key={{{payload.apikey}}}&callback=initMap\">\n</script>\n    \n</body>\n</html>","x":638.000057220459,"y":169.0000286102295,"wires":[["422d2000.76f7d"]]},{"id":"ef2d665c.ae6b98","type":"comment","z":"934da042.4fbe4","name":"Inject msg object properties (API Key)","info":"","x":253.00003051757812,"y":263.0000286102295,"wires":[]},{"id":"f757b3ad.2c45","type":"comment","z":"934da042.4fbe4","name":"http://localhost:1880/monitor","info":"","x":201,"y":127.00000095367432,"wires":[]},{"id":"c8427880.258498","type":"comment","z":"934da042.4fbe4","name":"Alert Message to Browser","info":"","x":666.0000076293945,"y":349.00000190734863,"wires":[]},{"id":"4293d71c.08c428","type":"comment","z":"934da042.4fbe4","name":"New Brunswick Leak Monitoring Site","info":"","x":419,"y":62,"wires":[]},{"id":"b5ef4df0.46d12","type":"comment","z":"934da042.4fbe4","name":"IoT Event Listener ","info":"","x":176.16665649414062,"y":343.11116790771484,"wires":[]},{"id":"85ee3376.744af","type":"inject","z":"934da042.4fbe4","name":"","topic":"","payload":"","payloadType":"date","repeat":"5","crontab":"","once":false,"onceDelay":0.1,"x":192.16665267944336,"y":420.8888921737671,"wires":[["bb248168.03a0a"]]},{"id":"bb248168.03a0a","type":"function","z":"934da042.4fbe4","name":"Alert Generator","func":"var msg ={};\n\nvar count = Math.floor(Math.random() * Math.floor(4));\n\nswitch ( count ) {\ncase 0 : \n  msg.payload = {\n    addr : '1 RichMond St',\n    la : 40.494181,\n    lo : -74.440144,\n    type : 'Water',\n    severity : 3\n  };\n  break;\ncase 1 :\n  msg.payload = {\n    addr : '1-21 Oxford St',\n    la : 40.492229,\n    lo : -74.457641,\n    type : 'Water',\n    severity : 1\n  };\n  break;\ncase 2 :\n  msg.payload = {\n    addr : '104-70 Guilden St',\n    la : 40.500203,\n    lo : -74.456846,\n    type : 'Gas',\n    severity : 3\n  };\n  break;\ncase 3 :\n  msg.payload = {\n    addr : '9-165 Edgeworth Pl',\n    la : 40.483208,\n    lo : -74.454688,\n    type : 'Gas',\n    severity : 3\n  };\n  break;\ndefault :\n  msg.payload = {\n    addr : '300 Somerset St',\n    la : 40.492735,\n    lo : -74.456407,\n    type : 'Water',\n    severity : 2\n  };\n  break;\n\n}\n\nreturn msg;","outputs":1,"noerr":0,"x":421.1666679382324,"y":421.33333587646484,"wires":[["8c59d981.361258","a0978def.ec19"]]},{"id":"7c9b20fd.7d9be","type":"debug","z":"934da042.4fbe4","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":423.1666717529297,"y":536.7777824401855,"wires":[]},{"id":"8c59d981.361258","type":"debug","z":"934da042.4fbe4","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":669.1666717529297,"y":458.7778072357178,"wires":[]},{"id":"a0978def.ec19","type":"websocket out","z":"934da042.4fbe4","name":"","server":"5e53441f.11d6ac","client":"","x":666.1666717529297,"y":388.6667232513428,"wires":[]},{"id":"1dbde980.69ccf7","type":"websocket in","z":"934da042.4fbe4","name":"","server":"5e53441f.11d6ac","client":"","x":163.16665649414062,"y":537.1112232208252,"wires":[["7c9b20fd.7d9be"]]},{"id":"b5c0a096.911a8","type":"comment","z":"934da042.4fbe4","name":"Coomunicate with Browser","info":"","x":197.16665649414062,"y":496.3333377838135,"wires":[]},{"id":"13758f8c.9d682","type":"comment","z":"934da042.4fbe4","name":"Example : MQTT message from MQTT Broker","info":"","x":255.16665649414062,"y":378.3194465637207,"wires":[]},{"id":"5e53441f.11d6ac","type":"websocket-listener","z":"","path":"/ws/alert","wholemsg":"false"}]

Screen

New pricing changes go into effect starting July 16, 2018. Get-API-Key

Once Google API-Key is available,

In Function node,

Assign msg.payload.apikey <- Google Map API Key

In HTML template,

<script async defer src="https://maps.googleapis.com/maps/api/js?key={{{payload.apikey}}}&callback=initMap" type="text/javascript"></script>

Read files from the local storage

For example,

  1. Set httpStatic in settings.js to $HOME/.node_red/dist.
  2. Place picture.jpg' in $HOME/.node_red/dist/images`
  3. <img src="/images/picture.jpg"> in index.html

If CDN options are not desirable, use httpStatic and adjust src attribute.

About

template node to create the dashboard (+ google maps )

https://phyunsj.github.io