diff --git a/public/index.html b/public/index.html index 79f3bd98f..82103d9e5 100644 --- a/public/index.html +++ b/public/index.html @@ -303,6 +303,7 @@ + @@ -317,6 +318,5 @@ - diff --git a/public/red/comms.js b/public/red/comms.js index f20e42032..418f65150 100644 --- a/public/red/comms.js +++ b/public/red/comms.js @@ -18,24 +18,34 @@ RED.comms = function() { var errornotification = null; var subscriptions = {}; - + var ws; function connectWS() { var path = location.hostname+":"+location.port+document.location.pathname; path = path+(path.slice(-1) == "/"?"":"/")+"comms"; path = "ws"+(document.location.protocol=="https:"?"s":"")+"://"+path; - var ws = new WebSocket(path); + ws = new WebSocket(path); ws.onopen = function() { if (errornotification) { errornotification.close(); errornotification = null; } + for (var t in subscriptions) { + ws.send(JSON.stringify({subscribe:t})); + } } ws.onmessage = function(event) { var msg = JSON.parse(event.data); - var subscribers = subscriptions[msg.topic]; - if (subscribers) { - for (var i=0;i 0) { @@ -137,25 +137,34 @@ var RED = function() { function loadSettings() { $.get('settings', function(data) { - RED.settings = data; - loadNodes(); + RED.settings = data; + loadNodes(); }); } function loadNodes() { $.get('nodes', function(data) { - $("body").append(data); - $(".palette-spinner").hide(); - $(".palette-scroll").show(); - $("#palette-search").show(); - loadFlows(); + $("body").append(data); + $(".palette-spinner").hide(); + $(".palette-scroll").show(); + $("#palette-search").show(); + loadFlows(); }); } function loadFlows() { $.getJSON("flows",function(nodes) { - RED.nodes.import(nodes); - RED.view.dirty(false); - RED.view.redraw(); + RED.nodes.import(nodes); + RED.view.dirty(false); + RED.view.redraw(); + RED.comms.subscribe("status/#",function(topic,msg) { + var parts = topic.split("/"); + var node = RED.nodes.node(parts[1]); + if (node) { + node.status = msg; + node.dirty = true; + RED.view.redraw(); + } + }); }); } @@ -168,18 +177,19 @@ var RED = function() { //}); dialog.on('show',function() { - RED.keyboard.disable(); + RED.keyboard.disable(); }); dialog.on('hidden',function() { - RED.keyboard.enable(); + RED.keyboard.enable(); }); dialog.modal(); } $(function() { - RED.keyboard.add(/* ? */ 191,{shift:true},function(){showHelp();d3.event.preventDefault();}); - loadSettings(); + RED.keyboard.add(/* ? */ 191,{shift:true},function(){showHelp();d3.event.preventDefault();}); + loadSettings(); + RED.comms.connect(); }); return { diff --git a/public/red/ui/view.js b/public/red/ui/view.js index 264a4597d..78431d697 100644 --- a/public/red/ui/view.js +++ b/public/red/ui/view.js @@ -44,6 +44,12 @@ RED.view = function() { var clipboard = ""; + + var status_colours = { + "red" : "#00c", + "green": "#5a8" + } + var outer = d3.select("#chart") .append("svg:svg") .attr("width", space_width) @@ -811,7 +817,7 @@ RED.view = function() { .on("click",function(d) { d._def.onbadgeclick.call(d);d3.event.preventDefault();}); } } - + if (d._def.button) { var nodeButtonGroup = node.append('svg:g') .attr("transform",function(d) { return "translate("+((d._def.align == "right") ? 94 : -25)+",2)"; }) @@ -893,6 +899,21 @@ RED.view = function() { text.attr('text-anchor','end'); } + var status = node.append("svg:g").attr("class","node_status_group").style("display","none"); + + var statusRect = status.append("circle").attr("class","node_status") + .attr("cx",9).attr("cy",6).attr("r",5).attr("stroke-width","3"); + var statusLabel = status.append("svg:text") + .attr("class","node_status_label") + .attr('x',20).attr('y',10) + .style({ + 'stroke-width': 0, + 'fill': '#999', + 'font-size':'9pt', + 'stroke':'#000', + 'text-anchor':'start' + }); + //node.append("circle").attr({"class":"centerDot","cx":0,"cy":0,"r":5}); if (d._def.inputs > 0) { @@ -1017,7 +1038,37 @@ RED.view = function() { } return ""; }); - + if (!d.status) { + thisNode.selectAll('.node_status_group').style("display","none"); + } else { + thisNode.selectAll('.node_status_group').style("display","inline").attr("transform","translate(3,"+(d.h+3)+")"); + var fill = status_colours[d.status.fill]; // Only allow our colours for now + if (d.status.shape == null && fill == null) { + thisNode.selectAll('.node_status').style("display","none"); + } else { + var style; + if (d.status.shape == null || d.status.shape == "dot") { + style = { + display: "inline", + fill: fill, + stroke: fill + }; + } else if (d.status.shape == "ring" ){ + style = { + display: "inline", + fill: '#fff', + stroke: fill + } + } + thisNode.selectAll('.node_status').style(style); + } + if (d.status.text) { + thisNode.selectAll('.node_status_label').text(d.status.text); + } else { + thisNode.selectAll('.node_status_label').text(""); + } + } + d.dirty = false; } }); diff --git a/red/comms.js b/red/comms.js index 7641eaaf8..d1bd98649 100644 --- a/red/comms.js +++ b/red/comms.js @@ -23,6 +23,8 @@ var settings; var wsServer; var activeConnections = []; +var retained = {}; + var heartbeatTimer; var lastSentTime; @@ -47,6 +49,12 @@ function start() { } } }); + ws.on('message', function(data,flags) { + var msg = JSON.parse(data); + if (msg.subscribe) { + handleRemoteSubscription(ws,msg.subscribe); + } + }); ws.on('error', function(err) { util.log("[red:comms] error : "+err.toString()); }); @@ -67,17 +75,34 @@ function start() { }, 15000); } -function publish(topic,data) { - var msg = JSON.stringify({topic:topic,data:data}); +function publish(topic,data,retain) { + if (retain) { + retained[topic] = data; + } + lastSentTime = Date.now(); activeConnections.forEach(function(conn) { - try { - conn.send(msg); - } catch(err) { - util.log("[red:comms] send error : "+err.toString()); - } + publishTo(conn,topic,data); }); } +function publishTo(ws,topic,data) { + var msg = JSON.stringify({topic:topic,data:data}); + try { + ws.send(msg); + } catch(err) { + util.log("[red:comms] send error : "+err.toString()); + } +} + +function handleRemoteSubscription(ws,topic) { + var re = new RegExp("^"+topic.replace(/([\[\]\?\(\)\\\\$\^\*\.|])/g,"\\$1").replace(/\+/g,"[^/]+").replace(/\/#$/,"(\/.*)?")+"$"); + for (var t in retained) { + if (re.test(t)) { + publishTo(ws,t,retained[t]); + } + } +} + module.exports = { init:init, diff --git a/red/nodes/Node.js b/red/nodes/Node.js index a21e8d8fa..83c8f937a 100644 --- a/red/nodes/Node.js +++ b/red/nodes/Node.js @@ -20,7 +20,7 @@ var clone = require("clone"); var flows = require("./flows"); - +var comms = require("../comms"); function Node(n) { this.id = n.id; @@ -114,6 +114,11 @@ Node.prototype.error = function(msg) { if (this.name) o.name = this.name; this.emit("log",o); } - +/** + * status: { fill:"red|green", shape:"dot|ring", text:"blah" } + */ +Node.prototype.status = function(status,retain) { + comms.publish("status/"+this.id,status,retain); +} module.exports = Node; diff --git a/red/red.js b/red/red.js index 14a0d9a50..87ad63bcc 100644 --- a/red/red.js +++ b/red/red.js @@ -18,7 +18,6 @@ var events = require("./events"); var server = require("./server"); var nodes = require("./nodes"); var library = require("./library"); -var comms = require("./comms"); var log = require("./log"); var fs = require("fs"); var settings = null; @@ -51,7 +50,6 @@ var RED = { stop: server.stop, nodes: nodes, library: library, - comms: comms, events: events, log: log };